NSGA-II 的多目标优化算法的实现与解析,代码免费~

声明:本文仅用于学习交流用途

文章里面有详细的代码全文~

基于 NSGA-II 的多目标优化算法的实现与解析

本文详细介绍了一种使用遗传算法进行多目标优化的实现,该算法基于NSGA-II(Non-dominated Sorting Genetic Algorithm II),用于优化两个目标函数。本文将逐步解释整个实现的每一部分,包括种群初始化、非支配排序、拥挤度计算、选择、交叉与变异操作等。

1. 引言

多目标优化问题广泛应用于工程、经济学和科学研究等领域。这类问题的典型特征是有多个相互冲突的目标需要同时优化。遗传算法(Genetic Algorithm, GA)是一种基于进化思想的随机搜索算法,NSGA-II是其中一种用于解决多目标优化问题的经典方法。该算法通过快速非支配排序和拥挤距离计算,能在一个迭代过程中生成Pareto前沿解集。本文使用NSGA-II算法对两个目标函数进行优化,代码中详细实现了种群初始化、选择、交叉、变异、排序等步骤。

2. 问题定义与种群初始化

2.1 问题定义

代码首先定义了种群的规模(pop)、迭代次数(gen)、目标函数的数量(M)、以及决策变量的维度(V)。在此示例中,目标函数数量M设为2,意味着我们要优化两个目标函数,而决策变量的维度V设为2,表示我们有两个自变量。每个决策变量的取值范围由VarMinVarMax定义,这里决策变量的取值范围在-5到5之间。

matlab 复制代码
pop = 100; % 种群数量
gen = 50;  % 迭代次数
M = 2;     % 目标函数数量
V = 2;     % 决策变量维度
VarMin = -5*ones(1, V); % 决策变量的下界
VarMax = 5*ones(1,V);   % 决策变量的上界

2.2 种群初始化

种群初始化是遗传算法的第一步。在代码中,通过随机生成每个个体的决策变量值来创建初始种群。每个个体是一个长度为V的向量,对应于V个决策变量。为了确保生成的个体在合法的变量范围内,使用了random('Uniform', VarMin(j), VarMax(j))生成均匀分布的随机数,并调用了test函数进行范围约束检查。

每个个体的决策变量生成后,都会计算其目标函数值,并存储在chromosome矩阵中。chromosome的前V列存储决策变量,后M列存储目标函数值。

matlab 复制代码
for i=1:pop
    f=[];
    flag = 1;
    while flag==1
        for j=1:numel(VarMin)
            f(j) = random('Uniform', VarMin(j), VarMax(j));
        end
        chromosome(i,1:V)=f;
        flag = test(f,VarMin,VarMax); % 检查个体是否在合法范围内
    end
    chromosome(i,V+1:V+M)=CostFunction(chromosome(i,1:V)); % 计算目标函数值
end

在种群生成后,调用non_domination_sort_mod函数对种群进行初始的非支配排序并计算拥挤度。

matlab 复制代码
chromosome = non_domination_sort_mod(chromosome, M, V); % 非支配排序与拥挤度计算

3. 选择、交叉和变异操作

3.1 选择操作

遗传算法中,选择操作决定了哪些个体能够参与后续的交叉和变异操作。在该实现中,使用了竞标赛选择(Tournament Selection)方法。在每轮选择中,随机从种群中抽取两个个体进行比较,选择排序级别更高(非支配等级低)的个体。如果两个个体的排序级别相同,则选择拥挤度更大的个体。该过程在tournament_selection函数中实现。

matlab 复制代码
parent_chromosome = tournament_selection(chromosome, pool, tour);

3.2 交叉操作

在交叉操作中,代码使用了模拟二进制交叉(Simulated Binary Crossover, SBX)方法。该方法可以生成与父代相似但不完全相同的子代,从而探索解空间。交叉概率pc控制交叉操作的发生频率。两个父代个体在交叉时,会以一定概率交换决策变量值,从而生成两个新的子代个体。

matlab 复制代码
offspring_chromosome = genetic_operator(parent_chromosome, M, V, mu, mum, VarMin, VarMax, pc, pm);

3.3 变异操作

变异操作引入随机性,以防止算法陷入局部最优解。代码采用多项式变异(Polynomial Mutation),通过一定的概率pm对子代个体的决策变量进行扰动。变异后,子代的目标函数值也会相应更新。

4. 非支配排序与拥挤度计算

在每一代迭代中,子代和父代将合并成一个新的种群,并重新进行非支配排序与拥挤度计算。non_domination_sort_mod函数的作用是对种群进行非支配排序,并为每个个体分配拥挤距离。非支配排序将个体根据其支配关系分为多个层次,层次越低的个体具有更好的非支配性。拥挤距离用于衡量个体在目标空间中的稀疏度,拥挤距离越大的个体越优先保留,以增加解的多样性。

matlab 复制代码
intermediate_chromosome = non_domination_sort_mod(intermediate_chromosome, M, V);

5. 精英保留策略与种群更新

遗传算法中,精英保留策略确保最优个体不被丢弃。通过对父代和子代合并后的种群进行排序,选取排序等级高且拥挤距离大的个体组成新的种群,这一步通过replace_chromosome函数实现。

matlab 复制代码
chromosome = replace_chromosome(intermediate_chromosome, M, V, pop);

6. 结果展示

在算法的最后,代码会输出种群的Pareto最优前沿。如果优化目标是两个,结果会以二维平面展示;如果是三个目标,结果则以三维空间展示。

matlab 复制代码
if M == 2
    plot(chromosome(:,V + 1),chromosome(:,V + 2),'o');
    xlabel('f_1'); ylabel('f_2');
    title('Pareto Optimal Front');
elseif M == 3
    plot3(chromosome(:,V + 1),chromosome(:,V + 2),chromosome(:,V + 3),'*');
    xlabel('f_1'); ylabel('f_2'); zlabel('f_3');
    title('Pareto Optimal Surface');
end

当 M ==2时,输出的结果如下:

7. 总结

本文详细介绍了一个基于NSGA-II算法的多目标优化实现。通过对遗传算法的种群初始化、选择、交叉、变异以及非支配排序等步骤的解析,展示了如何通过进化思想解决多目标优化问题。这一实现能够有效地生成Pareto最优解集,并通过拥挤距离保持解的多样性,为解决实际中的多目标问题提供了强有力的工具。


8. 代码全文

代码全文如下:

matlab 复制代码
clc,clear,close all

%% 问题定义

pop = 100; %种群数量
gen = 50; %迭代次数
M = 2; %目标函数数量
V = 2; %维度(决策变量的个数)
VarMin = -5*ones(1, V); %下界
VarMax = 5*ones(1,V); %上界 
pc=0.9;pm=0.1; % 交叉、变异

%% 种群初始化

for i=1:pop
    f=[];
    flag = 1;
    while flag==1
        for j=1:numel(VarMin)
            f(j) = random('Uniform', VarMin(j), VarMax(j));
        end
        chromosome(i,1:V)=f;
        flag = test(f,VarMin,VarMax);
    end
    chromosome(i,V+1:V+M)=CostFunction(chromosome(i,1:V));
end
chromosome = non_domination_sort_mod(chromosome, M, V);%对初始化种群进行非支配快速排序和拥挤度计算


tic;
for i = 1 : gen
    pool = round(pop/2);%round() 四舍五入取整 交配池大小
    tour = 2;%竞标赛  参赛选手个数
    parent_chromosome = tournament_selection(chromosome, pool, tour);%竞标赛选择适合繁殖的父代
    %交叉和变异算法的分布指数
    mu = 20;%交叉和变异算法的分布指数
    mum = 20;
    
    offspring_chromosome = genetic_operator(parent_chromosome,M, V,mu, mum, VarMin, VarMax,pc,pm);%进行交叉变异产生子代 该代码中使用模拟二进制交叉和多项式变异 采用实数编码
    [main_pop,~] = size(chromosome);%父代种群的大小
    [offspring_pop,~] = size(offspring_chromosome);%子代种群的大小
    
    clear temp
    intermediate_chromosome(1:main_pop,:) = chromosome;
    intermediate_chromosome(main_pop + 1 : main_pop + offspring_pop,1 : M+V) = offspring_chromosome;%合并父代种群和子代种群
    intermediate_chromosome = non_domination_sort_mod(intermediate_chromosome, M, V);%对新的种群进行快速非支配排序
    chromosome = replace_chromosome(intermediate_chromosome, M, V, pop);%选择合并种群中前N个优先的个体组成新种群
    if ~mod(i,100)
        clc;
        fprintf('%d generations completed\n',i);
    end
end
toc;
disp(['运行时间: ',num2str(toc)]);
xlswrite('NSGA2gaijin',chromosome);
if M == 2
    plot(chromosome(:,V + 1),chromosome(:,V + 2),'o');
    xlabel('f_1'); ylabel('f_2');
    title('Pareto Optimal Front');
elseif M == 3
    plot3(chromosome(:,V + 1),chromosome(:,V + 2),chromosome(:,V + 3),'*');
    xlabel('f_1'); ylabel('f_2'); zlabel('f_3');
    title('Pareto Optimal Surface');
end



function f = tournament_selection(chromosome, pool_size, tour_size)
[pop, variables] = size(chromosome);%获得种群的个体数量和决策变量数量
rank = variables - 1;%个体向量中排序值所在位置
distance = variables;%个体向量中拥挤度所在位置
%竞标赛选择法,每次随机选择两个个体,优先选择排序等级高的个体,如果排序等级一样,优选选择拥挤度大的个体
for i = 1 : pool_size
    for j = 1 : tour_size
        candidate(j) = round(pop*rand(1));%随机选择参赛个体
        if candidate(j) == 0
            candidate(j) = 1;
        end
        if j > 1
            while ~isempty(find(candidate(1 : j - 1) == candidate(j)))%防止两个参赛个体是同一个
                candidate(j) = round(pop*rand(1));
                if candidate(j) == 0
                    candidate(j) = 1;
                end
            end
        end
    end
    for j = 1 : tour_size% 记录每个参赛者的排序等级 拥挤度
        c_obj_rank(j) = chromosome(candidate(j),rank);
        c_obj_distance(j) = chromosome(candidate(j),distance);
    end
    min_candidate = ...
        find(c_obj_rank == min(c_obj_rank));%选择排序等级较小的参赛者,find返回该参赛者的索引
    if length(min_candidate) ~= 1%如果两个参赛者的排序等级相等 则继续比较拥挤度 优先选择拥挤度大的个体
        max_candidate = ...
        find(c_obj_distance(min_candidate) == max(c_obj_distance(min_candidate)));
        if length(max_candidate) ~= 1
            max_candidate = max_candidate(1);
        end
        f(i,:) = chromosome(candidate(min_candidate(max_candidate)),:);
    else
        f(i,:) = chromosome(candidate(min_candidate(1)),:);
    end
end
end


function flag = test(f,VarMin,VarMax)

    n = size(VarMin,2);
    
    flag=1;
    
    for i=1:n
        %
        if f(i)>=VarMin(i) && f(i)<=VarMax(i)
            % 满足
            flag=0;
        else
            flag=1;
        end
    end
end

        
function f  = replace_chromosome(intermediate_chromosome, M, V,pop)%精英选择策略

[N, m] = size(intermediate_chromosome);
[temp,index] = sort(intermediate_chromosome(:,M + V + 1));

clear temp m
for i = 1 : N
    sorted_chromosome(i,:) = intermediate_chromosome(index(i),:);
end

max_rank = max(intermediate_chromosome(:,M + V + 1));

previous_index = 0;
for i = 1 : max_rank
    current_index = max(find(sorted_chromosome(:,M + V + 1) == i));
    if current_index > pop
        remaining = pop - previous_index;
        temp_pop = ...
            sorted_chromosome(previous_index + 1 : current_index, :);
        [temp_sort,temp_sort_index] = ...
            sort(temp_pop(:, M + V + 2),'descend');
        for j = 1 : remaining
            f(previous_index + j,:) = temp_pop(temp_sort_index(j),:);
        end
        return;
    elseif current_index < pop
        f(previous_index + 1 : current_index, :) = ...
            sorted_chromosome(previous_index + 1 : current_index, :);
    else
        f(previous_index + 1 : current_index, :) = ...
            sorted_chromosome(previous_index + 1 : current_index, :);
        return;
    end
    previous_index = current_index;
end
end


%% 对初始种群开始排序 快速非支配排序
% 使用非支配排序对种群进行排序。该函数返回每个个体对应的排序值和拥挤距离,是一个两列的矩阵。  
% 并将排序值和拥挤距离添加到染色体矩阵中 
function f = non_domination_sort_mod(x, M, V)
[N, ~] = size(x);% N为矩阵x的行数,也是种群的数量
clear m
front = 1;
F(front).f = [];
individual = [];

for i = 1 : N
    individual(i).n = 0;%n是个体i被支配的个体数量
    individual(i).p = [];%p是被个体i支配的个体集合
    for j = 1 : N
        dom_less = 0;
        dom_equal = 0;
        dom_more = 0;
        for k = 1 : M        %判断个体i和个体j的支配关系
            if (x(i,V + k) < x(j,V + k))  
                dom_less = dom_less + 1;
            elseif (x(i,V + k) == x(j,V + k))
                dom_equal = dom_equal + 1;
            else
                dom_more = dom_more + 1;
            end
        end
        if dom_less == 0 && dom_equal ~= M % 说明i受j支配,相应的n加1
            individual(i).n = individual(i).n + 1;
        elseif dom_more == 0 && dom_equal ~= M % 说明i支配j,把j加入i的支配合集中
            individual(i).p = [individual(i).p j];
        end
    end   
    if individual(i).n == 0 %个体i非支配等级排序最高,属于当前最优解集,相应的染色体中携带代表排序数的信息
        x(i,M + V + 1) = 1;
        F(front).f = [F(front).f i];%等级为1的非支配解集
    end
end
%上面的代码是为了找出等级最高的非支配解集
%下面的代码是为了给其他个体进行分级
while ~isempty(F(front).f)
   Q = []; %存放下一个front集合
   for i = 1 : length(F(front).f)%循环当前支配解集中的个体
       if ~isempty(individual(F(front).f(i)).p)%个体i有自己所支配的解集
        	for j = 1 : length(individual(F(front).f(i)).p)%循环个体i所支配解集中的个体
            	individual(individual(F(front).f(i)).p(j)).n = ...%...表示的是与下一行代码是相连的, 这里表示个体j的被支配个数减1
                	individual(individual(F(front).f(i)).p(j)).n - 1;
        	   	 if individual(individual(F(front).f(i)).p(j)).n == 0% 如果q是非支配解集,则放入集合Q中
               		x(individual(F(front).f(i)).p(j),M + V + 1) = ...%个体染色体中加入分级信息
                        front + 1;
                    Q = [Q individual(F(front).f(i)).p(j)];
                end
            end
       end
   end
   front =  front + 1;
   F(front).f = Q;
end

[temp,index_of_fronts] = sort(x(:,M + V + 1));%对个体的代表排序等级的列向量进行升序排序 index_of_fronts表示排序后的值对应原来的索引
for i = 1 : length(index_of_fronts)
    sorted_based_on_front(i,:) = x(index_of_fronts(i),:);%sorted_based_on_front中存放的是x矩阵按照排序等级升序排序后的矩阵
end
current_index = 0;

%% Crowding distance 计算每个个体的拥挤度

for front = 1 : (length(F) - 1)%这里减1是因为代码55行这里,F的最后一个元素为空,这样才能跳出循环。所以一共有length-1个排序等级
    distance = 0;
    y = [];
    previous_index = current_index + 1;
    for i = 1 : length(F(front).f)
        y(i,:) = sorted_based_on_front(current_index + i,:);%y中存放的是排序等级为front的集合矩阵
    end
    current_index = current_index + i;%current_index =i
    sorted_based_on_objective = [];%存放基于拥挤距离排序的矩阵
    for i = 1 : M
        [sorted_based_on_objective, index_of_objectives] = ...
            sort(y(:,V + i));%按照目标函数值排序
        sorted_based_on_objective = [];
        for j = 1 : length(index_of_objectives)
            sorted_based_on_objective(j,:) = y(index_of_objectives(j),:);% sorted_based_on_objective存放按照目标函数值排序后的x矩阵
        end
        f_max = ...
            sorted_based_on_objective(length(index_of_objectives), V + i);%fmax为目标函数最大值 fmin为目标函数最小值
        f_min = sorted_based_on_objective(1, V + i);
        y(index_of_objectives(length(index_of_objectives)),M + V + 1 + i)...%对排序后的第一个个体和最后一个个体的距离设为无穷大
            = Inf;
        y(index_of_objectives(1),M + V + 1 + i) = Inf;
         for j = 2 : length(index_of_objectives) - 1%循环集合中除了第一个和最后一个的个体
            next_obj  = sorted_based_on_objective(j + 1,V + i);
            previous_obj  = sorted_based_on_objective(j - 1,V + i);
            if (f_max - f_min == 0)
                y(index_of_objectives(j),M + V + 1 + i) = Inf;
            else
                y(index_of_objectives(j),M + V + 1 + i) = ...
                     (next_obj - previous_obj)/(f_max - f_min);
            end
         end
    end
    distance = [];
    distance(:,1) = zeros(length(F(front).f),1);
    for i = 1 : M
        distance(:,1) = distance(:,1) + y(:,M + V + 1 + i);
    end
    y(:,M + V + 2) = distance;
    y = y(:,1 : M + V + 2);
    z(previous_index:current_index,:) = y;
end
f = z();%得到的是已经包含等级和拥挤度的种群矩阵 并且已经按等级排序排序
end



function f  = genetic_operator(parent_chromosome, M, V, mu,mum, VarMin, VarMax,pc,pm)
[N,m] = size(parent_chromosome);%N是交配池中的个体数量

%clear m
p = 1;
%下面代码找出交配池中非支配等级的最大值和最小值 为自适应概率计算做准备

%%首先进行交叉工作
for i = 1 : N%这里虽然循环N次,但是每次循环都会有概率产生2个子代,所以最终产生的子代个体数量是2N个
    child_1 = [];
    child_2 = [];
    parent_1 = round(N*rand(1));
    if parent_1 < 1
        parent_1 = 1;
    end
    parent_2 = round(N*rand(1));
    if parent_2 < 1
        parent_2 = 1;
    end
    while isequal(parent_chromosome(parent_1,:),parent_chromosome(parent_2,:))
        parent_2 = round(N*rand(1));
        if parent_2 < 1
            parent_2 = 1;
        end
    end
    parent_1 = parent_chromosome(parent_1,:);
    parent_2 = parent_chromosome(parent_2,:);
    child_1=parent_1;
    child_2=parent_2;
    
    if rand(1) <pc%交叉概率0.9
        flag1=1;
        flag2=1;
        while flag1==1 || flag2==1
            for j = 1 : V
                
                u(j) = rand(1);
                if u(j) <= 0.5
                    bq(j) = (2*u(j))^(1/(mu+1));
                else
                    bq(j) = (1/(2*(1 - u(j))))^(1/(mu+1));
                end
                child_1(j) = ...
                    0.5*(((1 + bq(j))*parent_1(j)) + (1 - bq(j))*parent_2(j));
                child_2(j) = ...
                    0.5*(((1 - bq(j))*parent_1(j)) + (1 + bq(j))*parent_2(j));
                if child_1(j) > VarMax(j)
                    child_1(j) = VarMax(j);
                elseif child_1(j) < VarMin(j)
                    child_1(j) = VarMin(j);
                end
                if child_2(j) > VarMax(j)
                    child_2(j) = VarMax(j);
                elseif child_2(j) < VarMin(j)
                    child_2(j) = VarMin(j);
                end
            end
            
            flag1 = test(child_1,VarMin,VarMax);
            flag2 = test(child_2,VarMin,VarMax);
        end
        child_1(:,V + 1: M + V)=CostFunction(child_1(1:V));
        child_2(:,V + 1: M + V)=CostFunction(child_2(1:V));
    end
    child(p,:) =  child_1(:, 1: M + V);
    child(p+1,:) = child_2(:, 1: M + V);
    p = p + 2;
end
[S,L] = size(child);
pp=1;
%%对交叉后的数组的每个个体根据概率进行变异操作
for jj=1:S
    child_3 = child(pp,:);
    if rand(1)<pm
        flag=1;
        while flag==1
            for ji = 1 : V
                r(ji) = rand(1);
                if r(ji) < 0.5
                    delta(ji) = (2*r(ji))^(1/(mum+1)) - 1;
                else
                    delta(ji) = 1 - (2*(1 - r(ji)))^(1/(mum+1));
                end
                
                child_3(ji) = child_3(ji) + delta(ji);
                if child_3(ji) > VarMax(ji) % 条件约束
                    child_3(ji) = VarMax(ji);
                elseif child_3(ji) < VarMin(ji)
                    child_3(ji) = VarMin(ji);
                end
            end
            flag = test(child_3,VarMin,VarMax);
        end
        child_3(:,V + 1: M + V)=CostFunction(child_3(1:V));
    end
    child(pp,:) = child_3(:,1:M+V);
    pp=pp+1;
end

f = child;
end

function f = CostFunction(x)
    f(1) = x(1)^4 - 10*x(1)^2+x(1)*x(2) + x(2)^4 - (x(1)^2)*(x(2)^2);
    f(2) = x(2)^4 - (x(1)^2)*(x(2)^2) + x(1)^4 + x(1)*x(2);
end

欢迎感兴趣的读者和作者沟通交流,qq:2414342361

相关推荐
带多刺的玫瑰1 小时前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔1 小时前
《线性代数的本质》
线性代数·算法·决策树
yigan_Eins1 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
阿史大杯茶1 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
დ旧言~2 小时前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
张彦峰ZYF2 小时前
投资策略规划最优决策分析
分布式·算法·金融
The_Ticker2 小时前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
爪哇学长3 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
Dola_Pan3 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
繁依Fanyi3 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse