一、引言
在上一個篇章中,我們用遺傳算法來計算一個一元函數的最大值,但是,有人會講,這樣是不是有些大材小用了,明明我可以用更少的代碼來實現求最大值的功能。確實,將遺傳算法用在那里確實大材小用了,但是,博主的目的並不是為求最大值,而是為了給大家展示,遺傳算法是一種可行的算法,並且博主編寫的matlab代碼是可行的。現在,我們就要利用上一篇寫的代碼作為基本代碼,來解決本篇的問題。放心,本篇的問題可不是用普通的方法就能就解決的。遺傳算法的春天終於來了。讓我們開始吧。。
二、問題描述和建模
負載均衡調度問題:假設有N個任務,需要負載均衡器分配給M個服務器節點去處理。每個任務的任務長度、每台服務器節點(下面簡稱“節點”)的處理速度已知,請給出一種任務分配方式,使得所有任務的總處理時間最短。
好,那么現在題目有了,但是我們要想辦法對題目進行數學建模。顯然,我們需要2個矩陣,一個存放任務(含任務數量、任務長度),一個存放服務器節點(含節點數量、處理速度)。
(1)任務長度矩陣(簡稱:任務矩陣)
我們將所有任務的任務長度用矩陣tasks表示,其中tasks(i)中的i表示任務的編號,tasks(i)表示任務i的任務長度。比如
tasks = [2, 4, 6, 8]
表明,任務1的長度是2;任務2的長度是4,以此類推。
(2)節點處理速度矩陣(簡稱:節點矩陣)
我們將所有服務器節點的處理速度用nodes表示,其中nodes(i)中的i表示節點的編號,nodes(i)表示節點j的處理速度。比如
nodes = [2, 1]
表明,節點1的速度是2;節點2的速度是1,以此類推。
(3)任務處理時間矩陣
當任務矩陣task和節點矩陣nodes確定下來后,那么所有任務分配給所有節點的任務處理時間就可以確定了,我們用矩陣timeMat來存所有節點的任務處理時間,那么timeMat(i, j)表示任務i分配給節點j處理所需的時間,它通過以下公式計算:
timeMat(i, j) = task(i) / nodes(j);
有了上面三個矩陣后,我們就算是成功將題目用數學的方式描述出來了。
我們用matlab代碼創建一個createData.m文件,用來對題目進行建模並且保存數據。如下:
%% I. 清空自變量
clear all
clc
%% II. 題目建模
% 參數定義
taskNum = 100; % 任務數量
taskLengthRange = [10, 100]; % 任務長度范圍
nodeNum = 10; % 節點數量
nodeSpeedRange = [10, 100]; % 節點長度范圍
tasks = initRandomMat(taskNum, taskLengthRange); % 構建任務矩陣(含任務數量和任務長度)
nodes = initRandomMat(nodeNum, nodeSpeedRange); % 構建節點矩陣(含節點數量和節點處理速度)
timeMat = zeros(taskNum, nodeNum); % 構建處理時間矩陣
for i = 1: taskNum
for j = 1: nodeNum
timeMat(i, j) = tasks(1, i) / nodes(1, j);
end
end
%% III. 數據保存
save('data', 'tasks', 'nodes', 'timeMat');
其中initRandomMat()函數代碼如下:
% [name] -- initRandomMat(初始化隨機矩陣)
% [function]--
% [input] -- length (數組的長度)
% -- range (數組元素的取值范圍range(1) to range(2))
% [output] -- randomMat
function randomMat = initRandomMat(length, range)
randomMat = zeros(1, length);
for i = 1: length
randomMat(1, i) = round(rand() * (range(2) - range(1)) + range(1));
end
三、遺傳算法解決問題
還是老樣子,我們先回顧下遺傳算法的步驟,如下:
1. 種群初始化
2. 計算每個種群的適應度值
3. 選擇(Selection)
4. 交叉(Crossover)
5. 變異(Mutation)
6. 重復2-5步直至達到進化次數
正如我們上一篇所說,為了讓我們的代碼有更好的包容性,我們需要先寫幾個函數包,到時再main.m里面引用,就很方便了。
(1)種群初始化函數popInit(),能根據提供的種群大小和染色體長度產生初始的種群。代碼如下:
% [name] -- popInit(種群初始化函數) % [function]-- 構建種群矩陣,其中行數為種群個數,列數為染色體長度(即基因的個數),並隨機分配染色體的樣式 % [input] -- 1. popSize(種群大小/個數) % -- 2. cLength(染色體長度) % -- 3. range (染色體上基因的數值范圍0 - range) % [output] -- popMat(種群矩陣) function popMat = popInit(popSize, cLength, range) popMat = zeros(popSize, cLength); % 預分配內存 for i = 1: popSize for j = 1: cLength popMat(i, j) = ceil(rand() * range); % rand產生(1,range)之間的隨機數 end end clear i; clear j;
(2)計算每個種群的適應度值函數getfitnessValue()。代碼如下:
% [name] -- getfitnessValue(計算種群個體適應度值)
% [function]-- 根據不同的題目要求,設計適應度方程計算的算法
% -- 在實際的負載均衡調度中,各個節點的任務處理是並行計算的,所以,所有任務的完成時間應該是所有節點任務完成時間的最大值,並非所有任務完成時間的總和。
% [input] -- timeMat (時間處理矩陣) -- timeMat(i, j): 第i個任務分配到第j個節點,需要的處理時間
% -- popMat (種群矩陣) -- popMat(i, j): 第i個染色體上, 第j個任務分配到的節點標號
% -- nodeNumMax (節點的最大標號)
% [output] -- fitValMat(每個染色體的適應度值)
function fitnessValueMatrix = getfitnessValue(popMat, timeMat, nodeNumMax)
[popSize, cLength] = size(popMat); % 獲取種群的數目(行)和染色體長度(列)
fitnessValueMatrix = zeros(popSize, 1); % 初始化適應度矩陣 % 最大的節點標號
for i = 1: popSize
maxLength = eps; % 大於0的最小正數
for nodeIndex = 1: nodeNumMax
sumLength = 0; % 清零
for j = 1: cLength
if popMat(i, j) == nodeIndex
sumLength = sumLength + timeMat(j, nodeIndex);
end
end
if sumLength > maxLength
maxLength = sumLength;
end
end
fitnessValueMatrix(i) = maxLength;
end
clear i;
clear j;
clear nodeIndex;
(3)選擇函數selection(),可以對種群進行選擇,具體算法和代碼如下:
% [name] -- selection(選擇操作)
% [function]-- 采用輪盤賭的一個形式來進行選擇,增加不確定性,這樣種群就不容易趨向局部最優
% [input] -- 1. fitnessValueMatrix (適應度值矩陣)
% -- 2. popMat(未選擇的種群矩陣)
% -- 3. type -- 1: 保留親代最優個體
% 0: 不保留親代最優個體
% [output] -- updatedPopMat(經選擇后的種群矩陣)
function updatedPopMat = selection(fitnessValueMatrix, popMat, type)
[popSize, cLength] = size(popMat);
updatedPopMat = zeros(popSize, cLength);
fitnessValueMatrix = 100 ./ fitnessValueMatrix;
% 剔除適應值為負的種群,使其適應值為0
for i = 1: popSize
if fitnessValueMatrix(i, 1) < 0
fitnessValueMatrix(i, 1) = 0;
end
end
% 輪盤賭算法
P = fitnessValueMatrix / sum(fitnessValueMatrix);
Pc = cumsum(P);
for i = 1: popSize
index = find(Pc >= rand);
updatedPopMat(i, :) = popMat(index(1), :);
end
% 是否要保留親本適應度值最高的,若是,則type = 1,否則為0
if type
[~, bestIndex] = max(fitnessValueMatrix);
updatedPopMat(popSize, :) = popMat(bestIndex, :); % 將親代最優染色體放到子代的最后一個個體中
end
clear i;
clear j;
(4)交叉函數crossover(),可以對種群進行交叉,交叉的方式又分為單點交叉和多點交叉,根據輸入不同的參數來選擇不同的實現方式,具體算法和代碼如下:
% [name] -- crossover(交叉操作)
% [function]-- 選定交叉點並進行互換
% [input] -- 1. popMat (未交叉的種群矩陣)
% -- 2. type -- 1: 單點交叉
% -- 2: 多點交叉
% -- 3. crossrate (交叉率) -- 建議值為0.6
% [output] -- updatedPopMat(經交叉后的種群矩陣)
function updatedPopMat = crossover(popMat, type, crossrate)
[popSize, cLength] = size(popMat);
if type == 1
% 單點交叉
for i = 1: 2: popSize
if crossrate >= rand
crossPosition = round(rand() * (cLength - 2) + 2); % 隨機獲取交叉點,去除0和1
% 對 crossPosition及之后的二進制串進行交換
for j = crossPosition: cLength
temp = popMat(i, j);
popMat(i, j) = popMat(i + 1, j);
popMat(i + 1, j) = temp;
end
end
end
updatedPopMat = popMat;
elseif type == 2
% 多點交叉
for i = 1: 2: popSize
if crossrate >= rand
crossPosition1 = round(rand() * (cLength - 2) + 2); % 第一個交叉點
crossPosition2 = round(rand() * (cLength - 2) + 2); % 第二個交叉點
first = min(crossPosition1, crossPosition2);
last = max(crossPosition1, crossPosition2);
for j = first: last
temp = popMat(i, j);
popMat(i, j) = popMat(i + 1, j);
popMat(i + 1, j) = temp;
end
end
end
updatedPopMat = popMat;
else
h = errordlg('type的類型只能為1(單點交叉)或者2(多點交叉)', '進行交叉時發生錯誤');
end
clear i;
clear j;
(5)變異函數mutation(),可以對種群進行變異,具體算法和代碼如下:
% [name] -- mutation(變異操作) % [function]-- 單點變異:隨機選擇變異點,將其變為0或1 % [input] -- 1. popMat (未交叉的種群矩陣) % -- 2. mutateRate (交叉率) -- 建議值為0.1 % -- 3. randValur (變異后的隨機數范圍0-randValue) % [output] -- updatedPopMat(經交叉后的種群矩陣) function updatedPopMat = mutation(popMat, mutateRate, randValue) [popSize, cLength] = size(popMat); for i = 1: popSize if mutateRate >= rand mutatePosition = ceil(rand() * cLength); % 隨機獲取交叉點,去除0 % 對mutatePosition點進行變異 popMat(i, mutatePosition) = ceil(rand() * randValue); end end updatedPopMat = popMat; clear i; clear j;
(6)畫圖函數plotGraph:用於直觀的顯示在進化的過程中,種群平均總處理時間的變化。代碼如下:
% [name] -- plotGraph(畫圖)
% [function]-- 直觀的展示在進化過程中,平均適應度值的趨勢變化
% [input] -- 1. generationTime(進化次數)
% 2. fitnessAverageValueMatrix(平均適應度值矩陣)
% [output] -- none
function plotGraph(fitnessAverageValueMatrix, generationTime)
x = 1: 1: generationTime;
y = fitnessAverageValueMatrix;
plot(x, y);
xlabel('迭代次數');
ylabel('平均總處理時間');
title('種群平均總處理時間的進化曲線');
好了,至此,我們就完成了解決本題目需要的函數塊。接下來,我們只需要編寫主函數main.m,針對本章的題目,對其進行求解即可。代碼如下:
%% I. 清空變量
clear all
clc
%% II. 導入數據和參數初始化
%導入參數數據
tasks = load('data', 'tasks'); % 導入任務矩陣(含任務數量和任務長度)
tasks = tasks.tasks; % 導入的是struct格式,因此要再多此一步
nodes = load('data', 'nodes'); % 導入節點矩陣(含節點數量和節點處理速度)
nodes = nodes.nodes;
timeMat = load('data', 'timeMat'); % 導入處理時間矩陣
timeMat = timeMat.timeMat;
taskNum = length(tasks); % 任務數量
nodeNum = length(nodes); % 節點數量
iteratorNum = 1000; % 迭代次數
popSize = 300; % 種群大小(染色體數量)
crossRate = 0.6; % 交叉概率
mutateRate = 0.01; % 變異概率
fitnessAverageValueMatrix = zeros(iteratorNum, 1); % 每代平均適應度值
fitnessBestValueMatrix = zeros(iteratorNum, 1); % 每代最優適應度值
bestIndividual = zeros(iteratorNum, taskNum); % 每代最佳自變量
%% III. 初始化種群
popMat = popInit(popSize, taskNum, nodeNum);
%% IV. 迭代繁衍獲取更好的個體(選擇、交叉、變異)
for currentNum = 1: iteratorNum
% 求單個染色體的總處理時間和適應度矩陣
sumTimeMat = getfitnessValue(popMat, timeMat, nodeNum); % 染色體的總處理時間
% 記錄當前最好的數據
if currentNum == 1
[~, bestIndex] = min(sumTimeMat);
fitnessBestValueMatrix(currentNum) = min(sumTimeMat);
fitnessAverageValueMatrix(currentNum) = mean(sumTimeMat);
bestIndividual(currentNum, :)= popMat(bestIndex, :); % 最佳適應度值所對應的染色體(分配方案)
else
[~, bestIndex] = min(sumTimeMat);
fitnessBestValueMatrix(currentNum) = min(fitnessBestValueMatrix(currentNum - 1), min(sumTimeMat));
fitnessAverageValueMatrix(currentNum) = mean(sumTimeMat);
if fitnessBestValueMatrix(currentNum) == fitnessBestValueMatrix(currentNum - 1)
bestIndividual(currentNum, :) = bestIndividual(currentNum - 1, :);
elseif fitnessBestValueMatrix(currentNum) == min(sumTimeMat)
bestIndividual(currentNum, :) = popMat(bestIndex, :);
else
h = errordlg('請檢查代碼', '意外錯誤');
end
end
if currentNum ~= iteratorNum
% 選擇
popMat = selection(sumTimeMat, popMat, 1); % 保留親代最佳個體
% 交叉
popMat = crossover(popMat, 1, crossRate); % 單點交叉
% 變異
popMat = mutation(popMat, mutateRate, nodeNum);
end
end
%% V. 畫圖並展示結果
% 畫圖
plotGraph(fitnessAverageValueMatrix, iteratorNum);
% 展示數據
disp 最短處理時間
fitnessBestValueMatrix(currentNum, 1)
plan = bestIndividual(currentNum, :);
運行上述程序,可以得到:
ans =
9.5278
可以得知,經過我們的遺傳算法,可以設計出一套方案,讓總處理時間大大減小(不能保證是最小,但接近最小)。下面大家可以看看,隨着迭代次數的增加,遺傳算法對方案的優化進而平均總處理時間的變化。

可以看到,隨着迭代次數的增加,平均總處理時間從30多降到最后的9點多。
