PlatEMO代码解析
Created: January 16, 2024
Finished: January 17, 2024
Tags: main机器学习
进度: 已完成
以SparseEA解决ZDT1问题为例 platemo(‘algorithm’,@SparseEA,‘problem’,@ZDT1)
读取当前代码路径,D:\Project\ML\PlatEMO-master-EA\PlatEMO
之后将本路径下的所有文件夹都包括进调佣函数的目录
platemo_path = cd(fileparts(mfilename('fullpath')));
addpath(genpath(cd));
\color{#CD5C5C}\rule{740px}{3px}
if isempty(varargin)
if verLessThan('matlab','9.9')
errordlg('Fail to create the GUI of PlatEMO since the version for MATLAB is lower than R2020b. You can use PlatEMO without GUI by calling platemo() with parameters.','Error','modal');
else
try
GUI();
catch err
errordlg('Fail to create the GUI, please make sure all the folders of PlatEMO have been added to search path.','Error','modal');
rethrow(err);
end
end
else
if verLessThan('matlab','9.4')
error('Fail to use PlatEMO since the version for MATLAB is lower than R2018a. Please update your MATLAB software.');
else
[PRO,input] = getSetting(varargin);
Problem = PRO(input{:});
[ALG,input] = getSetting(varargin,Problem);
if nargout > 0
Algorithm = ALG(input{:},'save',0);
else
Algorithm = ALG(input{:});
end
Algorithm.Solve(Problem);
if nargout > 0
P = Algorithm.result{end};
varargout = {P.decs,P.objs,P.cons};
end
end
end
end
\color{#CD5C5C}\rule{740px}{3px}
函数解释
isempty函数
varargin是函数的输入变量,是所有参数组成的一个cell
isempty是值如果var为空,则isempty(varargin)为1,否则为0
因此,如果直接运行platemo,并且版本符合要求的话,会调用GUI()函数,生成图形化界面。
isempty(varargin)
\color{#FFAF33}\rule{740px}{2px}
[PRO,input] = getSetting(varargin) 解释
function [name,Setting] = getSetting(Setting,Pro)
isStr = find(cellfun(@ischar,Setting(1:end-1))&~cellfun(@isempty,Setting(2:end)));
if nargin > 1 %nargin代表输入参数数量
index = isStr(find(strcmp(Setting(isStr),'algorithm'),1)) + 1;
if isempty(index)
names = {@BSPGA,@GA,@SACOSO,@GA;@PMMOEA,@NSGAIII,@KRVEA,@NSGAIII;@RVEA,@RVEA,@CSEA,@RVEA};
name = names{find([Pro.M<2,Pro.M<4,1],1),find([all(Pro.encoding==4),any(Pro.encoding>2),Pro.maxFE<=1000&Pro.D<=10,1],1)};
elseif iscell(Setting{index})
name = Setting{index}{1};
Setting = [Setting,{'parameter'},{Setting{index}(2:end)}];
else
name = Setting{index};
end
else
index = isStr(find(strcmp(Setting(isStr),'problem'),1)) + 1;
if isempty(index)
name = @UserProblem;
elseif iscell(Setting{index})
name = Setting{index}{1};
Setting = [Setting,{'parameter'},{Setting{index}(2:end)}];
else
name = Setting{index};
end
end
end
var里面的内容是’algorithm’,@SparseEA,‘problem’,@ZDT1
所以,进入getsetting函数后,Setting变量被设置为’algorithm’,@SparseEA,‘problem’,@ZDT1,Pro没有值
cellfun(@ischar,Setting(1:end-1))
cellfun是为cell类型值应用fun函数
在本函数中,就是为Setting(1:end-1)数组应用ischar函数,得到的值为1 0 1
~cellfun(@isempty,Setting(2:end))
则是为Setting(2:end)数组应用isempty函数,得到的值为0 0 0 ,取反可得1 1 1
进行&合并之后,得到1 0 1
find()函数会返回非0元素的索引值 ,即1 3
isStr = find(cellfun(@ischar,Setting(1:end-1))&~cellfun(@isempty,Setting(2:end)));
之后由于nargin(输入参数数量)为1,所以进入了else条件中
经过运行,index的值为4
再次标记,isempty(a) a为空,则值为1,iscell(A) A为元胞数组,则值为1。
所以第一次调用getSetting返回的是name,即@ZDT1,也就是PRO的值,和Setting,即input的值
\color{#FFAF33}\rule{740px}{2px}
Problem = PRO(input{:}); 解释
properties
N = 100; % Population size
maxFE = 10000; % Maximum number of function evaluations
FE = 0; % Number of consumed function evaluations
end
properties(SetAccess = protected)
M; % Number of objectives
D; % Number of decision variables
maxRuntime = inf; % maximum runtime (in second)
encoding = 1; % Encoding scheme of each decision variable (1.real 2.integer 3.label 4.binary 5.permutation)
lower = 0; % Lower bound of each decision variable
upper = 1; % Upper bound of each decision variable
optimum; % Optimal values of the problem
PF; % Image of Pareto front
parameter = {}; % Other parameters of the problem
end
在进入PROBLEM.m文件中,应该运行了如上代码,是obj的参数赋值。
methods(Access = protected)
function obj = PROBLEM(varargin)
isStr = find(cellfun(@ischar,varargin(1:end-1))&~cellfun(@isempty,varargin(2:end)));
for i = isStr(ismember(varargin(isStr),{'N','M','D','maxFE','maxRuntime','parameter'}))
obj.(varargin{i}) = varargin{i+1};
end
obj.Setting();
obj.optimum = obj.GetOptimum(10000);
obj.PF = obj.GetPF();
end
end
这是matlab创建类语句,methods
Access=protected表示这个类是受保护的属性,即表示只有该类的成员方法,还有该类的子类可以访问该数据,类之外的函数或者脚本访问不了这个成员变量
methods(Access = protected)
**
end
for循环时,为 i 赋值,为[],所以进入不了for循环,直接运行到end
for i = isStr(ismember(varargin(isStr),{'N','M','D','maxFE','maxRuntime','parameter'}))
obj.(varargin{i}) = varargin{i+1};
end
进入obj.Setting()函数
%% Default settings of the problem
function Setting(obj)
obj.M = 2;
if isempty(obj.D); obj.D = 30; end
obj.lower = zeros(1,obj.D);
obj.upper = ones(1,obj.D);
obj.encoding = ones(1,obj.D);
end
ZDT1是PROBLEM的类中的值,obj.Setting()函数会进入到具体的问题去运行,得出来对应问题的目标数量,决策变量数量,上界和下界,以及编码方式。
obj.optimum = obj.GetOptimum(10000);函数
%% Generate points on the Pareto front
function R = GetOptimum(obj,N)
R(:,1) = linspace(0,1,N)';
R(:,2) = 1 - R(:,1).^0.5;
end
R(:,1) = linspace(0,1,N)';,生成0-1之间的10000个等距离点,然后进行转置
R(:,2) = 1 - R(:,1).^0.5; 使这10000个点具体到0-1之间
生成的两列值会保存到obj.optimum里面
obj.PF = obj.GetPF();函数
%% Generate the image of Pareto front
function R = GetPF(obj)
R = obj.GetOptimum(100);
end
得到R的值,存进obj.PF里面,得到了当前问题的Pareto前沿面
\color{#FFAF33}\rule{740px}{2px}
[ALG,input] = getSetting(varargin,Problem);函数
第二次进入函数,得到的值为ALG,ALG赋值为SparseEA。
\color{#FFAF33}\rule{740px}{2px}
Algorithm = ALG(input{:});
给Algorithm赋值
\color{#FFAF33}\rule{740px}{2px}
然后跑进了Algorithm.Solve(Problem);函数
obj.result = {};
obj.metric = struct('runtime',0);
obj.pro = Problem;
obj.pro.FE = 0;
addpath(fileparts(which(class(obj))));
addpath(fileparts(which(class(obj.pro))));
tic;
obj.main(obj.pro);
就是继续给obj赋值,然后跑进了obj.main(obj.pro)中,即具体的SparseEA代码中
function main(Algorithm,Problem)
%% Population initialization
% Calculate the fitness of each decision variable
TDec = [];
TMask = [];
TempPop = [];
ArcPop = [];
Fitness = zeros(1,Problem.D);
for i = 1 : 1+4*any(Problem.encoding~=4)
Dec = unifrnd(repmat(Problem.lower,Problem.D,1),repmat(Problem.upper,Problem.D,1));%生成0-1之间的30*30矩阵,D*D
Dec(:,Problem.encoding==4) = 1;
Mask = eye(Problem.D);
Population = Problem.Evaluation(Dec.*Mask);
TDec = [TDec;Dec];
TMask = [TMask;Mask];
TempPop = [TempPop,Population];
Fitness = Fitness + NDSort([Population.objs,Population.cons],inf);
end
% Generate initial population
Dec = unifrnd(repmat(Problem.lower,Problem.N,1),repmat(Problem.upper,Problem.N,1));
Dec(:,Problem.encoding==4) = 1;
Mask = false(Problem.N,Problem.D);
for i = 1 : Problem.N
Mask(i,TournamentSelection(2,ceil(rand*Problem.D),Fitness)) = 1;
end
Population = Problem.Evaluation(Dec.*Mask);
[Population,Dec,Mask] = EnvironmentalSelection([Population,TempPop],[Dec;TDec],[Mask;TMask],Problem.N);
%% Optimization
while Algorithm.NotTerminated(Population)
%AllPop = [Population,ArcPop];
MatingPool = randi(Problem.N,1,Problem.N);
%MatingPool = MatingSelection(Population,ArcPop,Problem.N);
%MatingPool = TournamentSelection(2,2*Problem.N,FrontNo,-CrowdDis);
[OffDec,OffMask] = Operator(Problem,Dec(MatingPool,:),Mask(MatingPool,:),Fitness);
Offspring = Problem.Evaluation(OffDec.*OffMask);
%ArcPop = UpdateArc([AllPop,Offspring],Problem.N);
[Population,Dec,Mask] = EnvironmentalSelection([Population,Offspring],[Dec;OffDec],[Mask;OffMask],Problem.N);
Allpop = [Population Offspring];
N = min(Problem.N,length(Allpop));
[FrontNo,MaxFNo] = NDSort(Allpop.objs,N);
%% Association operation
Next = [find(FrontNo<MaxFNo),find(FrontNo==MaxFNo)];
Choose = Association(Allpop(FrontNo<MaxFNo).objs,Allpop(FrontNo==MaxFNo).objs,N);
Next = Next(Choose);
score = max(Next) - Next + 1;
MaskScore = Mask .* repmat(score, [Problem.D 1])';
decScore = sum(MaskScore);
Fitness = max(decScore) - decScore + 1;
end
end
Population = Problem.Evaluation(Dec.*Mask)函数解释
计算种群的各项参数值,
function Population = Evaluation(obj,varargin)
PopDec = obj.CalDec(varargin{1});
PopObj = obj.CalObj(PopDec);
PopCon = obj.CalCon(PopDec);
Population = SOLUTION(PopDec,PopObj,PopCon,varargin{2:end});
obj.FE = obj.FE + length(Population);
end
PopDec = obj.CalDec(varargin{1});函数
function PopDec = CalDec(obj,PopDec)
Type = arrayfun(@(i)find(obj.encoding==i),1:5,'UniformOutput',false);
index = [Type{1:3}];
if ~isempty(index)
PopDec(:,index) = max(min(PopDec(:,index),repmat(obj.upper(index),size(PopDec,1),1)),repmat(obj.lower(index),size(PopDec,1),1));
end
index = [Type{2:5}];
if ~isempty(index)
PopDec(:,index) = round(PopDec(:,index));
end
end
PopObj = obj.CalObj(PopDec);函数是进入了ZDT1.m文件中运行的
%% Calculate objective values
function PopObj = CalObj(obj,PopDec)
PopObj(:,1) = PopDec(:,1);
g = 1 + 9*mean(PopDec(:,2:end),2);
h = 1 - (PopObj(:,1)./g).^0.5;
PopObj(:,2) = g.*h;
end
PopCon = obj.CalCon(PopDec);函数
function PopCon = CalCon(obj,PopDec)
PopCon = zeros(size(PopDec,1),1);
end
Population = SOLUTION(PopDec,PopObj,PopCon,varargin{2:end});函数是将这几个变量都保存到Population结构体中
obj.FE = obj.FE + length(Population);表示适应度评价次数为30次
然后就是在循环中不断对种群进行处理,知道到达了终止条件
\color{#FFAF33}\rule{740px}{2px}
画图函数其实是在Algorithm.NotTerminated(Population)这个里面的
obj.metric.runtime = obj.metric.runtime + toc;
if obj.pro.maxRuntime < inf
obj.pro.maxFE = obj.pro.FE*obj.pro.maxRuntime/obj.metric.runtime;
end
num = max(1,abs(obj.save));
index = max(1,min(min(num,size(obj.result,1)+1),ceil(num*obj.pro.FE/obj.pro.maxFE)));
obj.result(index,:) = {obj.pro.FE,Population};
drawnow('limitrate');
obj.outputFcn(obj,obj.pro);
nofinish = obj.pro.FE < obj.pro.maxFE;
assert(nofinish,'PlatEMO:Termination',''); tic;
然后最重要的是,有这么一句强制执行的
outputFcn = @DefaultOutput; % Function called after each generation
所以会进入DefaultOutput函数,最后输出的数据和图像都在这个函数中可以看到,就运行完毕了
function DefaultOutput(Algorithm,Problem)
% The default output function of ALGORITHM
clc; fprintf('%s on %d-objective %d-variable %s (%6.2f%%), %.2fs passed...\n',class(Algorithm),Problem.M,Problem.D,class(Problem),Problem.FE/Problem.maxFE*100,Algorithm.metric.runtime);
if Problem.FE >= Problem.maxFE
if Algorithm.save < 0
if Problem.M > 1
Population = Algorithm.result{end};
if length(Population) >= size(Problem.optimum,1); name = 'HV'; else; name = 'IGD'; end
value = Algorithm.CalMetric(name);
figure('NumberTitle','off','Name',sprintf('%s : %.4e Runtime : %.2fs',name,value(end),Algorithm.CalMetric('runtime')));
title(sprintf('%s on %s',class(Algorithm),class(Problem)),'Interpreter','none');
top = uimenu(gcf,'Label','Data source');
g = uimenu(top,'Label','Population (obj.)','CallBack',{@(h,~,Pro,P)eval('Draw(gca);Pro.DrawObj(P);cb_menu(h);'),Problem,Population});
uimenu(top,'Label','Population (dec.)','CallBack',{@(h,~,Pro,P)eval('Draw(gca);Pro.DrawDec(P);cb_menu(h);'),Problem,Population});
uimenu(top,'Label','True Pareto front','CallBack',{@(h,~,P)eval('Draw(gca);Draw(P,{''\it f\rm_1'',''\it f\rm_2'',''\it f\rm_3''});cb_menu(h);'),Problem.optimum});
cellfun(@(s)uimenu(top,'Label',s,'CallBack',{@(h,~,A)eval('Draw(gca);Draw(A.CalMetric(h.Label),''-k.'',''LineWidth'',1.5,''MarkerSize'',10,{''Number of function evaluations'',strrep(h.Label,''_'','' ''),[]});cb_menu(h);'),Algorithm}),{'IGD','HV','GD','Feasible_rate'});
set(top.Children(4),'Separator','on');
g.Callback{1}(g,[],Problem,Population);
else
best = Algorithm.CalMetric('Min_value');
if isempty(best); best = nan; end
figure('NumberTitle','off','Name',sprintf('Min value : %.4e Runtime : %.2fs',best(end),Algorithm.CalMetric('runtime')));
title(sprintf('%s on %s',class(Algorithm),class(Problem)),'Interpreter','none');
top = uimenu(gcf,'Label','Data source');
uimenu(top,'Label','Population (dec.)','CallBack',{@(h,~,Pro,P)eval('Draw(gca);Pro.DrawDec(P);cb_menu(h);'),Problem,Algorithm.result{end}});
cellfun(@(s)uimenu(top,'Label',s,'CallBack',{@(h,~,A)eval('Draw(gca);Draw(A.CalMetric(h.Label),''-k.'',''LineWidth'',1.5,''MarkerSize'',10,{''Number of function evaluations'',strrep(h.Label,''_'','' ''),[]});cb_menu(h);'),Algorithm}),{'Min_value','Feasible_rate'});
set(top.Children(2),'Separator','on');
top.Children(2).Callback{1}(top.Children(2),[],Algorithm);
end
elseif Algorithm.save > 0
folder = fullfile('Data',class(Algorithm));
[~,~] = mkdir(folder);
file = fullfile(folder,sprintf('%s_%s_M%d_D%d_',class(Algorithm),class(Problem),Problem.M,Problem.D));
runNo = 1;
while exist([file,num2str(runNo),'.mat'],'file') == 2
runNo = runNo + 1;
end
result = Algorithm.result;
metric = Algorithm.metric;
save([file,num2str(runNo),'.mat'],'result','metric');
end
end
end
多目标优化问题的Problem.M一定是大于1的,所以只分析多目标的一段
uimenu
在当前图窗中创建菜单,并返回 Menu
对象。
top = uimenu(gcf,'Label','Data source');
g = uimenu(top,'Label','Population (obj.)','CallBack',{@(h,~,Pro,P)eval('Draw(gca);Pro.DrawObj(P);cb_menu(h);'),Problem,Population});
uimenu(top,'Label','Population (dec.)','CallBack',{@(h,~,Pro,P)eval('Draw(gca);Pro.DrawDec(P);cb_menu(h);'),Problem,Population});
uimenu(top,'Label','True Pareto front','CallBack',{@(h,~,P)eval('Draw(gca);Draw(P,{''\it f\rm_1'',''\it f\rm_2'',''\it f\rm_3''});cb_menu(h);'),Problem.optimum});
cellfun(@(s)uimenu(top,'Label',s,'CallBack',{@(h,~,A)eval('Draw(gca);Draw(A.CalMetric(h.Label),''-k.'',''LineWidth'',1.5,''MarkerSize'',10,{''Number of function evaluations'',strrep(h.Label,''_'','' ''),[]});cb_menu(h);'),Algorithm}),{'IGD','HV','GD','Feasible_rate'});
如上代码是platemo可以画图的主要代码部分
uimenu创建了菜单栏,所以在生成图像中,才可以在data source下选择各项指标
接下来的每一句话其实都代表了每一项数据生成
eval的用法
eval可以动态执行命令,也就是说相当于可以捕捉代码执行过程中某个状态
上述代码也使用了eval,是指的变量不固定,Pro总是随着代码变化的
for i = 1:5
eval(['variable_' num2str(i) ' = i;']);
end
disp(variable_3); % 输出 3
disp(variable_5); % 输出 5
对’‘\it f\rm_1’‘,’‘\it f\rm_2’‘,’‘\it f\rm_3’’ 的解释: f 1 f 2 f 3 \it f\rm_1 \it f\rm_2 \it f\rm_3 f1f2f3
在latex中:\it 表示意大利体 \rm表示罗马体 即 f 是意大利体 1 是罗马体
\color{#FFAF33}\rule{740px}{2px}
OK,三个画图函数还是有分析的
function currentAxes = Draw(Data,varargin)
persistent ax;
if length(Data) == 1 && isgraphics(Data)
ax = Data;
cla(ax);
elseif ~isempty(Data) && ismatrix(Data)
if isempty(ax) || ~isgraphics(ax)
ax = gca;
end
if size(Data,2) == 1
Data = [(1:size(Data,1))',Data];
end
set(ax,'FontName','Times New Roman','FontSize',13,'NextPlot','add','Box','on','View',[0 90],'GridLineStyle','none');
if islogical(Data)
[ax.XLabel.String,ax.YLabel.String,ax.ZLabel.String] = deal('Solution No.','Dimension No.',[]);
elseif size(Data,2) > 3
[ax.XLabel.String,ax.YLabel.String,ax.ZLabel.String] = deal('Dimension No.','Value',[]);
elseif ~isempty(varargin) && iscell(varargin{end})
[ax.XLabel.String,ax.YLabel.String,ax.ZLabel.String] = deal(varargin{end}{:});
end
if ~isempty(varargin) && iscell(varargin{end})
varargin = varargin(1:end-1);
end
if isempty(varargin)
if islogical(Data)
varargin = {'EdgeColor','none'};
elseif size(Data,2) == 2
varargin = {'o','MarkerSize',6,'Marker','o','Markerfacecolor',[.7 .7 .7],'Markeredgecolor',[.4 .4 .4]};
elseif size(Data,2) == 3
varargin = {'o','MarkerSize',8,'Marker','o','Markerfacecolor',[.7 .7 .7],'Markeredgecolor',[.4 .4 .4]};
elseif size(Data,2) > 3
varargin = {'-','Color',[.5 .5 .5],'LineWidth',2};
end
end
if islogical(Data)
C = zeros(size(Data)) + 0.6;
C(~Data) = 1;
surf(ax,zeros(size(Data')),repmat(C',1,1,3),varargin{:});
elseif size(Data,2) == 2
plot(ax,Data(:,1),Data(:,2),varargin{:});
elseif size(Data,2) == 3
plot3(ax,Data(:,1),Data(:,2),Data(:,3),varargin{:});
view(ax,[135 30]);
elseif size(Data,2) > 3
Label = repmat([0.99,2:size(Data,2)-1,size(Data,2)+0.01],size(Data,1),1);
Data(2:2:end,:) = fliplr(Data(2:2:end,:));
Label(2:2:end,:) = fliplr(Label(2:2:end,:));
plot(ax,reshape(Label',[],1),reshape(Data',[],1),varargin{:});
end
axis(ax,'tight');
set(ax.Toolbar,'Visible','off');
set(ax.Toolbar,'Visible','on');
end
currentAxes = ax;
en
Draw函数的作用有两个,一个是选定合适的坐标系,另一个就是会把种群的数据点绘制到图像上
function DrawObj(obj,Population)
ax = Draw(Population.objs,{'\it f\rm_1','\it f\rm_2','\it f\rm_3'});
if ~isempty(obj.PF)
if ~iscell(obj.PF)
if obj.M == 2
plot(ax,obj.PF(:,1),obj.PF(:,2),'-k','LineWidth',1);
elseif obj.M == 3
plot3(ax,obj.PF(:,1),obj.PF(:,2),obj.PF(:,3),'-k','LineWidth',1);
end
else
if obj.M == 2
surf(ax,obj.PF{1},obj.PF{2},obj.PF{3},'EdgeColor','none','FaceColor',[.85 .85 .85]);
elseif obj.M == 3
surf(ax,obj.PF{1},obj.PF{2},obj.PF{3},'EdgeColor',[.8 .8 .8],'FaceColor','none');
end
set(ax,'Children',ax.Children(flip(1:end)));
end
elseif size(obj.optimum,1) > 1 && obj.M < 4
if obj.M == 2
plot(ax,obj.optimum(:,1),obj.optimum(:,2),'.k');
elseif obj.M == 3
plot3(ax,obj.optimum(:,1),obj.optimum(:,2),obj.optimum(:,3),'.k');
end
end
end
DrawObj函数的作用其实更偏向于根据Problem本身绘制真实的Pareto前沿面
function cb_menu(h)
% Switch between the selected menu
set(get(get(h,'Parent'),'Children'),'Checked','off');
set(h,'Checked','on');
end
本函数的作用就是选择对应的图像到对应的菜单栏中