一、GAs遺傳算法簡介
在計算機人工智能領域,遺傳算法算法是模擬自然選擇過程的啓發式搜索,這種啓發式(有時也被稱做元啓發式)通常用於產生優化和搜索的問題有用的解。遺傳算法屬於更大類別進化算法的一種,這類算法使用自然進化技術如繼承、變異、選擇和交叉獲得優化問題的解。
在機器學習上一個有用的觀點是學習作爲搜索問題,不同的學習方法用不同的搜索策略和潛在的需要搜索的假設空間結構進行特徵化。在遺傳算法中假設經常是用位字符串進行描述,具體的解釋依賴於具體的應用,除此之外也可以用符合表達式甚至是計算機程序表示。從一個初始假設的種羣(集合)開始搜索一個合適假設,當前種羣的成員通過模擬生物進化過程包括隨機突變和交叉操作產生下一代種羣。在每一步,當前種羣的假設根據給定的適應值進行進行評估,最適應的假設被概率性的選擇作爲產生下一代的種子。遺傳算法已經被成功的應用到各種學習問題和其他的優化問題,例如,遺傳算法已經被用於機器人控制的規則集合學習以及爲人工神經網絡優化拓撲結構和學習參數。
遺傳算法產生後繼假設是通過重複的突變和重新組合當前種羣中已知的最好的假設,而非從一般——特別的假設或者從簡單——複雜的假設。這個過程形成了一個假設的生產——測試的束搜索,其中不變的是當前種羣中的最適應的假設是下一個最可能被考慮的。遺傳算法流行的原因包括
1.對於在生物系統中適應性進化是已知成功的和魯棒的方法;
2.遺傳算法可以搜索包含複雜交互作用部分的假設空間,其中每一部分對總體假設適應值的影響可能很難模擬;
3.遺傳算法很容易並行化,可以利用計算機硬件成本減少的優勢。
二、本文遺傳算法的4個實現版本
遺傳算法的實現版本很多,本文主要介紹4個:
1.MichellAlgorith:《Machine Learning》一書中米切爾敘述的版本,算法首先根據設的定種羣大小隨機生成一個初始種羣,根據預設的適應值計算方法,計算每個假設的適應值;模擬生物進化操作包括選擇,交叉和變異;選擇根據種羣中每個假設的適應值概率性選擇固定比例的假設到下一代種羣中;交叉是與選擇並行進行的,交叉選擇的個體也是從原種羣中根據假設的適應值概率性的選擇,每次選擇兩個個體進行交叉產生兩個後代,交叉的次數等於種羣的大小減去選擇得到的種羣數量除以2,算法實現時可能要進行奇偶數處理;變異是在完成選擇和交叉之後得到的新種羣上進行的操作,根據米切爾的敘述使用固定比例的突變數目進行突變,這種情況適應於在突變率確定的情況下種羣數量較大的情況,在本文實現時採用非固定比例的突變數,概率性的進行突變,這樣在種羣數量少時也保證了可以有變異操作。
2.PlainAlgorithm:此算法是Matlab中通常被實現的版本,算法的思想主要是在進行選擇時選擇的數量的等於種羣的數量(種羣進化前後數量是固定的),之後再得到的種羣上進行隨機交叉操作,交叉的比例根據預先設定的交叉率;變異操與上述相同是對種羣中所有的個體進行變異操作。
3.ImprovedMichellAlgorithm與EfficientAlgorithm:算法思想基本相同,即是交叉時使用多點均勻交叉,選擇是根據預設的比例選擇種羣中適應值按從大到小排列前面的個體,其中EfficientAlgorithm算法是每次只選擇種羣中適應值最高的個體作爲精英保留,所以算法在實現時簡單而有效,交叉一方面是使用多點均勻交叉,另一方面交叉的個體選擇是先隨機從當前種羣中選擇指定數目的假設到競爭種羣中,在從競爭種羣中選擇最適應的假設作爲交叉的操作的父母,這裏ImprovedMichellAlgorithm交叉產生兩個後代,而EfficientAlgorithm交叉產生一個後代,所以交叉的次數等於種羣數量-1;最後在變異時兩種算法都不對保留的精英進行變異操作,這也是稱做精英算法的原因,已被證明是一個非常成功的遺傳算法改進版本,從本文實驗結果也可以解釋這一點。
三、UML圖和輔助類源代碼
1、UML類圖
2、UML活動圖
3、UML對象圖
3、UML順序圖
4、UML狀態圖
5、UML協作圖
6、UML用例圖
1.定義個體的類
package simpleGa;
class Individual {
/** 在public類中使用訪問方法,而非公共域 */
private int defalutGeneLength = 64;
private byte[] genes = new byte[defalutGeneLength];
private int fitness = 0;
/** 逐位調用隨機發送器,生成一個僞隨機二進制個體 */
void generateIndividual() {
byte gene;
for (int i = 0; i < size(); i++) {
gene = (byte)Math.round(Math.random());
genes[i] = gene;
}
}
/** 設置默認的個體基因編碼長度 */
void setDefaultGeneLength(int length) {
defalutGeneLength = length;
}
/** 獲取預設默認個體基因編碼長度 */
int getDefaultGeneLength() {
return defalutGeneLength;
}
/** 獲取個體指定位置的基因編碼 */
byte getGene(int index) {
return genes[index];
}
/** 設置個體指定位置的基因編碼 */
void setGene(int index, byte value) {
genes[index] = value;
}
/** 獲取個體編碼長度 */
int size() {
return genes.length;
}
/** 計算個體的適應值 */
int getFitness() {
if (fitness == 0)
fitness = FitnessCalc.getFitness(this);
return fitness;
}
/** 覆蓋java.lang.Object.toString()方法,獲得個體的字符串編碼*/
public String toString() {
String geneString = "";
for (int i = 0; i < size(); i++)
geneString += getGene(i);
return geneString;
}
}
2.定義種羣的類
package simpleGa;
class Population implements Cloneable {
private Individual[] individuals;
/** 構造器 */
public Population(int populationSize, boolean initialise) {
individuals = new Individual[populationSize];
// 初始化種羣
if (initialise) {
//循環創建種羣中的所有個體
for (int i = 0; i < size(); i++) {
Individual newIndividual = new Individual();
newIndividual.generateIndividual();
saveIndividual(i, newIndividual);
}
}
}
/** 根據給定的數組索引,返回該位置的個體*/
Individual getIndividual(int index) {
return individuals[index];
}
/** 計算種羣中適應值最大的個體 */
Individual getFittest() {
Individual fittest = getIndividual(0);
for (int i = 0; i < size(); i++)
if (fittest.getFitness() < getIndividual(i).getFitness())
fittest = getIndividual(i);
return fittest;
}
/**
* 得到種羣中從位置k開始的適應值最高的個體;
* 連續調用此方法得到前n個適應值最大的個體是時需要保持原種羣的秩序
*/
MyHashMap getFittest(int k) {
Individual fittest = getIndividual(k);
int index = k;
//生產一個MyHashMap,用於存放最大適應值的索引——個體鍵值對
MyHashMap myHashMap = new MyHashMap();
for (int i = k; i < size(); i++)
if (fittest.getFitness() < getIndividual(i).getFitness()) {
fittest = getIndividual(i);
index = i;
}
myHashMap.put(index, fittest);
return myHashMap;
}
/** 計算種羣中最適應的個體的索引位置 */
int getFittestIndex() {
Individual fittest = getIndividual(0);
int index = 0;
for (int i = 0; i < size(); i++) {
if (fittest.getFitness() < getIndividual(i).getFitness()) {
fittest = getIndividual(i);
index = i;
}
}
return index;
}
/** 計算種羣中存放個體的數組大小 */
int size() {
return individuals.length;
}
/** 保存指定的個體到指定的存放種羣的數組索引位置 */
void saveIndividual(int index, Individual indiv) {
individuals[index] = indiv;
}
/** 計算種羣的總適應值 */
int getTotalFitness() {
int sum = 0;
for (int i = 0; i < individuals.length; i++)
sum += getIndividual(i).getFitness();
return sum;
}
/** 覆蓋Object.colone()方法 */
protected Object clone() throws CloneNotSupportedException {
Population pop = (Population)super.clone();
pop.individuals = individuals.clone();
return pop;
}
/** 《Effective Java》總是覆蓋Object.toString()方法 */
public String toString() {
StringBuilder builder = new StringBuilder();
for (Individual indiv: individuals)
builder.append(indiv + "\n");
return builder.toString();
}
}
3.計算個體適應值的類
package simpleGa;
class FitnessCalc {
/** <<Effective Java>>:在public類中使用訪問方法而非,公共域 */
private static byte[] solution = new byte[64];
/** 設置候選解 */
static void setSolution(byte[] newSolution) {
solution = newSolution;
}
/** 獲取設置的候選解 */
byte[] getSolution() {
return solution;
}
/** 通過字符串編碼設置候選的解 */
static void setSolution(String newString) {
solution = new byte[newString.length()];
for (int i = 0; i < newString.length(); i++) {
String character = newString.substring(i, i+1);
if (character.contains("0") || character.contains("1"))
solution[i] = Byte.parseByte(character);
else
solution[i] = 0;
}
}
/** 計算個體的適應值 */
static int getFitness(Individual individual) {
int fitness = 0;
for (int i = 0; i < individual.size() && i < solution.length; i++) {
if (individual.getGene(i) == solution[i])
fitness++;
}
return fitness;
}
/** 獲取理論上可以進化得到的最大適應值即是預設的候選解的適應值 */
static int getMaxFitness() {
int maxFitness = solution.length;
return maxFitness;
}
}
4.整個程序運行常用的類
package simpleGa;
import java.util.Arrays;
import java.util.Random;
class Util {
private static int tournamentSize = 5;
/** 設置競爭種羣的大小 */
static void setTournamentSize(int size) {
tournamentSize = size;
}
/** 獲取預設的競爭種羣的大小 */
static int getTournamentSize() {
return tournamentSize;
}
/**
* 競爭選擇個體:隨機預設大小弟新種羣,從新種羣中返回一個適應值最大的個體;
* 在此遺傳算法系統實現中,一般用在交叉操作過程的個體選擇
*/
static Individual tournamentSelection(Population pop) {
Population tournament = new Population(tournamentSize, false);
for (int i = 0; i < tournamentSize; i++) {
int randomId = (int)(Math.random() * pop.size());
tournament.saveIndividual(i, pop.getIndividual(randomId));
}
Individual fittest = tournament.getFittest();
return fittest;
}
/** 費雪耶茲隨機置亂算法 */
static Population fisherYates(Population pop) {
Random random = new Random();
Individual temp;
for (int i = pop.size() - 1;i > 0; i--) {
int rand = random.nextInt(i+1); //隨機數範圍[0...i]
temp = pop.getIndividual(i);
pop.saveIndividual(i, pop.getIndividual(rand));
pop.saveIndividual(rand, temp);
}
random = null;
temp = null;
return pop;
}
/**
* 浮點數聚集求和如fitnessValue = [1 2 3 4],
* 則cumsum(fitnessValue) = [1 3 6 10]
*/
static double[] cumsum(double[] doubleArr) {
double[] tempArr = new double[doubleArr.length];
tempArr[0] = doubleArr[0];
for (int i = 1; i < doubleArr.length; i++)
tempArr[i] = doubleArr[i] + tempArr[i-1];
return tempArr;
}
/** 生成一個指定大小,並按從小到大排序的隨機浮點數組 */
static double[] sortedRandomArray(int len) {
double[] random = new double[len];
for (int i = 0; i < len; i++)
random[i] = Math.random();
//對生成的隨機數組按照從小到大排列
Arrays.sort(random);
return random;
}
/** 計算種羣的個體被選擇的概率聚集和 */
static double[] selectProbability(Population pop) {
//求種羣的適應值之和
int totalFitness = 0;
for (int i = 0; i < pop.size(); i++)
totalFitness += pop.getIndividual(i).getFitness();
//單個個體被選擇的概率
double[] cumPro = new double[pop.size()];
for (int i = 0; i < pop.size(); i++)
cumPro[i] = (double)pop.getIndividual(i).getFitness() / totalFitness;
//選擇概率的聚集求和
cumPro = Util.cumsum(cumPro);
return cumPro;
}
}
5.自定義的數據結構
package simpleGa;
import java.util.Arrays;
import java.util.Random;
class Util {
private static int tournamentSize = 5;
/** 設置競爭種羣的大小 */
static void setTournamentSize(int size) {
tournamentSize = size;
}
/** 獲取預設的競爭種羣的大小 */
static int getTournamentSize() {
return tournamentSize;
}
/**
* 競爭選擇個體:隨機預設大小弟新種羣,從新種羣中返回一個適應值最大的個體;
* 在此遺傳算法系統實現中,一般用在交叉操作過程的個體選擇
*/
static Individual tournamentSelection(Population pop) {
Population tournament = new Population(tournamentSize, false);
for (int i = 0; i < tournamentSize; i++) {
int randomId = (int)(Math.random() * pop.size());
tournament.saveIndividual(i, pop.getIndividual(randomId));
}
Individual fittest = tournament.getFittest();
return fittest;
}
/** 費雪耶茲隨機置亂算法 */
static Population fisherYates(Population pop) {
Random random = new Random();
Individual temp;
for (int i = pop.size() - 1;i > 0; i--) {
int rand = random.nextInt(i+1); //隨機數範圍[0...i]
temp = pop.getIndividual(i);
pop.saveIndividual(i, pop.getIndividual(rand));
pop.saveIndividual(rand, temp);
}
random = null;
temp = null;
return pop;
}
/**
* 浮點數聚集求和如fitnessValue = [1 2 3 4],
* 則cumsum(fitnessValue) = [1 3 6 10]
*/
static double[] cumsum(double[] doubleArr) {
double[] tempArr = new double[doubleArr.length];
tempArr[0] = doubleArr[0];
for (int i = 1; i < doubleArr.length; i++)
tempArr[i] = doubleArr[i] + tempArr[i-1];
return tempArr;
}
/** 生成一個指定大小,並按從小到大排序的隨機浮點數組 */
static double[] sortedRandomArray(int len) {
double[] random = new double[len];
for (int i = 0; i < len; i++)
random[i] = Math.random();
//對生成的隨機數組按照從小到大排列
Arrays.sort(random);
return random;
}
/** 計算種羣的個體被選擇的概率聚集和 */
static double[] selectProbability(Population pop) {
//求種羣的適應值之和
int totalFitness = 0;
for (int i = 0; i < pop.size(); i++)
totalFitness += pop.getIndividual(i).getFitness();
//單個個體被選擇的概率
double[] cumPro = new double[pop.size()];
for (int i = 0; i < pop.size(); i++)
cumPro[i] = (double)pop.getIndividual(i).getFitness() / totalFitness;
//選擇概率的聚集求和
cumPro = Util.cumsum(cumPro);
return cumPro;
}
}
6.測試算法運行的類
package simpleGa;
class GA {
/** 定義常量(《代碼大全》
* 代碼中不要出現magic數) */
private static final int POPULATION_SIZE = 100;
/** 減少類之間的耦合性;《設計模式:構建可複用的代碼》*/
private static final int MAX_ITERATIONS = 10000;
/** 主方法,測試遺傳算法性能
* @throws CloneNotSupportedException */
public static void main(String[] args) throws CloneNotSupportedException {
//設置候選解
FitnessCalc.setSolution("11110000000000000000000000000000000"
+ "00000000000000000000000001111");
//創建一個種羣,並進行初始化
Population myPop = new Population(POPULATION_SIZE, true);
//種羣代數計數
int generationCount = 0;
//創建一個Context類,維護一個Algorithm接口的引用
Context contextA, contextB, contextC, contextD;
//用一個具體的算法配置Context類
contextA = new Context(new EfficientAlgorithm());
contextB = new Context(new ImprovedMichellAlgorithm());
contextC = new Context(new MichellAlgorithm());
contextD = new Context(new PlainAlgorithm());
//進化直到獲得一個最優解
while (myPop.getFittest().getFitness() < FitnessCalc.getMaxFitness() &&
generationCount < MAX_ITERATIONS) {
generationCount++;
System.out.println("Generation: " + generationCount + " Fittest: " +
myPop.getFittest().getFitness());
//註釋掉最初建立系統時使用非策略方式的靜態調用方法
//myPop = ImprovedMichellAlgorithm.evolvePopulation(myPop);
//調用不同的算法進行實驗對比
myPop = contextA.executeAlgorithm(myPop);
// myPop = contextB.executeAlgorithm(myPop);
// myPop = contextC.executeAlgorithm(myPop);
// myPop = contextD.executeAlgorithm(myPop);
}
generationCount++;
if (myPop.getFittest().getFitness() >= FitnessCalc.getMaxFitness())
System.out.println("Solution found!");
System.out.println("Generation: " + generationCount);
System.out.println("Genes:");
System.out.println(myPop.getFittest());
System.out.println("Total fitenss:");
System.out.println(myPop.getTotalFitness());
}
}
四、4種算法流程圖及其源代碼
4種實現的算法採用了《設計模式》中的Strategy策略模式,除了實現算法的源文件包括MichellAlgorithm.java,PlainAlgorithm,java,ImprovedMichellAlgorithm.java和EfficientAlgorithm.java,還有Context.java和Algorithm接口;
Strategy模式中的Context類
package simpleGa;
/*
* 在遺傳算法實現時,使用了不同的實現方式,採用策略模式Strategy,對不同等算法進行調用;
* 策略模式由三個部分組成:
* 1.策略(Algorithm)
* 聲明一個支持所有算法的公共接口。環境類使用這個接口調用由具體策略定義的算法
* 2.具體策略(EfficientAlgorithm, ImprovedMichellAlgorithm, MichellAlgorithm, PlainAlgorithm)
* 使用策略接口實現算法
* 3.環境(Context)
* 由一個具體策略對象配置;維護着一個策略對象的接口;可能定義一個接口讓策略訪問它的數據
* Context類維護一個策略接口的引用,用一個具體的策略進行配置
*/
class Context {
private Algorithm algorithm;
/** 構造器;定義爲包私有的 */
Context(Algorithm algorithm) {
this.algorithm = algorithm;
}
/** 定義一個接口讓Algorithm訪問Context類的數據*/
Population executeAlgorithm(Population pop) throws CloneNotSupportedException {
return this.algorithm.evolvePopulation(pop);
}
}
各種算法共同實現的接口:
package simpleGa;
/*
* 《Introduction to Java Programming》:
* 接口中所有的域都是public static final int k = 1;所有的方法都是public abstract
*/
interface Algorithm {
/**
* 種羣的交叉比率
* 等價於public static final double crossoverRate = 0.7
*/
static final double CROSSOVER_RATE = 0.7;
/** 種羣的突變比率 */
static final double MUTATION_RATE = 0.015;
/**
* 種羣進化操作,在其實現的類中供其他的類調用的方法
* 等價於public abstract Population evolvePopulation(Population pop)
* @throws CloneNotSupportedException
*/
abstract Population evolvePopulation(Population pop) throws CloneNotSupportedException;
}
1.米切爾版本的遺傳算法
package simpleGa;
import java.util.Random;
/*
*
*/
class MichellAlgorithm implements Algorithm {
/**
* 種羣進化操作,可以被其他類訪問的方法
* 在機器學習創始人米切爾《Machine Learning》中選擇和交叉時並行操作,這裏實現時體現了這一算法思想
* @throws CloneNotSupportedException
*/
public Population evolvePopulation(Population pop) throws CloneNotSupportedException {
Population newPop = new Population(pop.size(), false);
//單個個體被選擇的概率;概率之和計算並非等於1.0
double[] indivSelectPro = Util.selectProbability(pop);
//選擇操作;相對《Machine Learning》原著中在選擇不是根據適應值概率性的進行選擇,而是選擇最大的前selectionSize的個體
newPop = select(pop);
//交叉操作
Population crossPop = crossover(pop, indivSelectPro);
int j = selectSize(pop);
for (int i = 0; i < crossPop.size(); i++, j++)
newPop.saveIndividual(j, crossPop.getIndividual(i));
//<<Effective Java>>:消除廢棄的對象引用
crossPop = null;
//突變操作
mutate(newPop);
return newPop;
}
/**
* 選擇適應值最高的精英到新種羣中
* 兩種方法:1.根據預設的選擇種羣數量連續選擇適應值最高的個體到新種羣中
* 2. 按照適應值從高到低的順序,依次選擇到新種羣中
* 這裏實現了第2種
* @throws CloneNotSupportedException
*/
private static Population select(Population pop)
throws CloneNotSupportedException {
//計算從種羣中選擇保留跨世精英數量
int selectionSize = selectSize(pop);
//創建一個非初始化原種羣大小的新種羣
Population newPop = new Population(pop.size(), false);
//保留種羣中適應值前selectSize數量的個體
Population copyPop = (Population)pop.clone();
for (int i = 0; i < selectionSize; i++) {
MyHashMap fittest = copyPop.getFittest(i);
newPop.saveIndividual(i, fittest.getValue());
//將從i到最後種羣中適應值最大的個體與第i位置個體進行交換,利用冒泡排序思想,從大到小排序,i之前的總是按適應值排好的
copyPop.saveIndividual(fittest.getKey(), copyPop.getIndividual(i));
copyPop.saveIndividual(i, fittest.getValue());
}
return newPop;
}
/** 交叉操作包括單點、兩點和均勻交叉;在這裏實現了單點交叉 */
private static Population crossover(Population pop, double[] fitValue) {
//計算交叉種羣的數量
int crossSize = crossSize(pop);
//創建一個非初始化的種羣,用於存放交叉操作得到的種羣
Population crossPop = new Population(crossSize, false);
//生成一個均勻分佈的僞隨機數組,用於隨機選擇交叉的個體;若交叉種羣的數量爲奇數時,用於確定最後一個加入到交叉種羣的個體
double[] random = Util.sortedRandomArray(crossSize);
//存放根據適應值概率性選擇的兩個個體
Individual indiv1 = new Individual();
Individual indiv2 = new Individual();
//兩個臨時變量存放一對個體產生的兩個交叉後代
Individual temp1 = new Individual();
Individual temp2 = new Individual();
//循環選擇crossSize/2對的個體進行交叉;
int j = 0;
for (int i = 0; i < crossSize - 1; i = i + 2) {
//根據兩個已經是從小到大順序的數組(隨機數數組和適應值數組),利用快速排序思想選擇兩個個體進行交叉
while(j < pop.size())
if (random[i] < fitValue[j]) {
indiv1 = pop.getIndividual(j);
break;//跳出循環
}
else
j++;
while (j < pop.size())
if (random[i+1] < fitValue[j]) {
indiv2 = pop.getIndividual(j);
break;//跳出循環
}
else
j++;
//計算個體的編碼長度
int len = pop.getIndividual(0).size();
//隨機選擇交叉點位置,範圍在[0...len-1]
int cpoint = (new Random()).nextInt(len);
//將選擇的兩個個體inidv1、indiv2的0...cpoint-1位置的基因設置分別爲temp1、temp2的0...cpoint-1的基因
int k;
for (k = 0; k < cpoint; k++) {
temp1.setGene(k, indiv1.getGene(k));
temp2.setGene(k, indiv2.getGene(k));
}
//將indiv1、indiv2的cpoint...len-1位置的基因設置爲temp2、temp1的cpoint...len-1位置的基因
for (; k < len; k++) {
temp1.setGene(k, indiv2.getGene(k));
temp2.setGene(k, indiv1.getGene(k));
}
//保存交叉得到的兩個後代到新種羣中
crossPop.saveIndividual(i, temp1);
crossPop.saveIndividual(i+1, temp2);
}
//<<Effective Java>>:消除廢棄的對象引用
temp1 = temp2 = indiv1 = indiv2 = null;
//判斷crossSize的奇偶性;若爲奇數,則根據個體適應值概率性的選擇一個個體到交叉種羣中
if (crossSize % 2 == 1)
while (j < pop.size())
if (random[crossSize-1] < fitValue[j]) {
crossPop.saveIndividual(crossSize - 1, pop.getIndividual(j));
break;//跳出循環
}
else
j++;
return crossPop;
}
/**
* 突變操作;當種羣數量小於一定數量時, 突變的可能性爲0;所以這個實現版本只適用於種羣數量較大時的遺傳算法;否則
* 算法必收斂到局部最優解。改進版本的遺傳算法是自適應的遺傳算法,突變率隨着種羣進化的次數增加而增加,在確保收斂
* 穩定性的情況下,可以跳過局部最優解;盡力搜索全局最優。
* 《Mahcine Learning》原著中算法突變操作思想是上述容,這裏實現時作了調整,突變的個體數量是概率性而非固定比例的
*/
private static void mutate(Population pop) {
// 對種羣的中非精英個體進行突變操作;《Machine Learning》原著中是對所有個體進行突變,包括種羣中的精英
// 因爲在原著中算法在第一步選擇時是根據個體的適應值,概率性的選擇固定指定數量的個體,而非絕對選擇適應值最前的個體;
// 與其的突變操作思想是一致的。
for (int i = selectSize(pop); i < pop.size(); i++) {
if (Math.random() < Algorithm.MUTATION_RATE) {
Individual indiv = pop.getIndividual(i);
//隨機選擇一個突變的位置
int randPos = (new Random()).nextInt(indiv.size());
//若突變的位置原始值爲1則反轉爲0,反之亦然;(《代碼大全》構建簡潔的代碼)
indiv.setGene(randPos, (byte)(1-indiv.getGene(randPos)));
}
}
}
/** 計算選擇種羣的大小 */
private static int selectSize(Population pop) {
return pop.size() - crossSize(pop);
}
/** 計算交叉種羣的大小 */
private static int crossSize(Population pop) {
return (int)Math.round(Algorithm.CROSSOVER_RATE * pop.size());
}
/**
* 計算突變個體的數量:當種羣數量比較小時,如果按照固定比例從種羣中隨機選擇個體進行變異,
* 且突變的比率自身又比較低,則會種羣進行突變的可能性爲0.在米切爾《Machine Learning》
* 中按固定比例選擇個體進行變異,在變異率確定的情況下,適應於種羣數量較大情況
*/
private static int mutateSize(Population pop) {
return (int)Math.round(pop.size() * Algorithm.MUTATION_RATE);
}
}
Ecplise中運行的結果:
package simpleGa;
import java.util.Random;
class PlainAlgorithm implements Algorithm {
/**
* 種羣進化操作,可以被其他類訪問的方法
* @throws CloneNotSupportedException
*/
public Population evolvePopulation(Population pop) throws CloneNotSupportedException {
Population newPop = new Population(pop.size(), false);
//單個個體被選擇的概率;概率之和計算並非等於1.0
double[] indivSelectPro = Util.selectProbability(pop);
//選擇操作
newPop = select(pop, indivSelectPro);
//對選擇得到的種羣進行交叉操作
newPop = crossover(newPop);//《Effective Java》減少創建的對象的引用
//突變操作
mutate(newPop);
return newPop;
}
/**
* 根據個體的適應值概率性的從當前種羣中,非放回可重複抽樣,選擇原種羣數量的新種羣
* @throws CloneNotSupportedException
*/
private static Population select(Population pop, double[] fitValue)
throws CloneNotSupportedException {
//創建一個非初始化原種羣大小的新種羣
Population newPop = new Population(pop.size(), false);
//生成一個均勻分佈的僞隨機數組,用於根據適應值概率性的選擇個體
double[] random = Util.sortedRandomArray(pop.size());
int randIndex = 0;
int fitIndex = 0;
while (randIndex < pop.size()) {
if (random[randIndex] < fitValue[fitIndex]) {
newPop.saveIndividual(randIndex, pop.getIndividual(randIndex));
randIndex++;
}
else
fitIndex++;
}
return newPop;
}
/** 交叉操作包括單點、兩點和均勻交叉;在這裏實現了單點交叉 */
private static Population crossover(Population pop) {
//創建一個非初始化的種羣
Population crossPop = new Population(pop.size(), false);
//存放根據適應值概率性選擇的兩個個體
Individual indiv1 = new Individual();
Individual indiv2 = new Individual();
//兩個臨時變量存放一對個體產生的兩個交叉後代
Individual temp1 = new Individual();
Individual temp2 = new Individual();
//計算個體的編碼長度
int len = pop.getIndividual(0).size();
//循環選擇pop.size()/2對的個體進行交叉;交叉使用單點交叉
for (int i = 0; i < pop.size() - 1; i = i + 2) {
indiv1 = pop.getIndividual(i);
indiv2 = pop.getIndividual(i+1);
if (Math.random() < Algorithm.CROSSOVER_RATE) {
//隨機選擇交叉點位置,範圍在[0...len-1]
int cpoint = (new Random()).nextInt(len);
//將選擇的兩個個體inidv1、indiv2的0...cpoint-1位置的基因設置分別
//爲temp1、temp2的0...cpoint-1的基因
int k;
for (k = 0; k < cpoint; k++) {
temp1.setGene(k, indiv1.getGene(k));
temp2.setGene(k, indiv2.getGene(k));
}
//將indiv1、indiv2的cpoint...len-1位置的基因設置爲temp2、temp1的cpoint...len-1位置的基因
for (; k < len; k++) {
temp1.setGene(k, indiv2.getGene(k));
temp2.setGene(k, indiv1.getGene(k));
}
}
else {
temp1 = indiv1;
temp2 = indiv2;
}
//保存交叉得到的兩個後代到新種羣中
crossPop.saveIndividual(i, temp1);
crossPop.saveIndividual(i+1, temp2);
}
//<<Effective Java>>:消除廢棄的對象引用
temp1 = temp2 = indiv1 = indiv2 = null;
//判斷種羣的奇偶性;若爲奇數,則根據個體適應值概率性的選擇一個個體到交叉種羣中
if (pop.size() % 2 == 1)
crossPop.saveIndividual(pop.size()-1, Util.tournamentSelection(pop));
return crossPop;
}
/**
* 突變操作:根據預設的突變率,對當前種羣中的個體進行變異
*/
private static void mutate(Population pop) {
// 對種羣的所有個體進行突變操作,包括非精英個體
for (int i = 0; i < pop.size(); i++) {
if (Math.random() < Algorithm.MUTATION_RATE) {
Individual indiv = pop.getIndividual(i);
//隨機選擇一個突變的位置
int randPos = (new Random()).nextInt(indiv.size());
//若突變的位置原始值爲1則反轉爲0,反之亦然;(《代碼大全》構建簡潔的代碼)
indiv.setGene(randPos, (byte)(1-indiv.getGene(randPos)));
}
}
}
}
Ecplise中運行的結果:
package simpleGa;
import java.util.Random;
class ImprovedMichellAlgorithm implements Algorithm{
/** 均勻概率 */
private static final double UNIFORM_RATE = 0.5;
/**
* 種羣進化操作,可以被其他類訪問的方法
* @throws CloneNotSupportedException
*/
public Population evolvePopulation(Population pop) throws CloneNotSupportedException {
Population newPop = new Population(pop.size(), false);
//單個個體被選擇的概率;概率之和計算並非等於1.0
double[] indivSelectPro = Util.selectProbability(pop);
//選擇操作
newPop = select(pop, indivSelectPro);
//交叉操作
Population crossPop = crossover(pop, indivSelectPro);
int j = selectSize(pop);
for (int i = 0; i < crossPop.size(); i++, j++)
newPop.saveIndividual(j, crossPop.getIndividual(i));
//<<Effective Java>>:消除廢棄的對象引用
crossPop = null;
//突變操作
mutate(newPop);
return newPop;
}
/**
* 選擇適應值最高的精英到新種羣中
* 兩種方法:1.根據預設的選擇種羣數量連續選擇適應值最高的個體到新種羣中
* 2. 按照適應值從高到低的順序,依次選擇到新種羣中
* 這裏實現了第2種
* @throws CloneNotSupportedException
*/
private static Population select(Population pop, double[] fitValue)
throws CloneNotSupportedException {
//計算從種羣中選擇保留跨世精英數量
int selectionSize = selectSize(pop);
//創建一個非初始化原種羣大小的新種羣
Population newPop = new Population(pop.size(), false);
//保留種羣中適應值前selectSize數量的個體
Population copyPop = (Population)pop.clone();
for (int i = 0; i < selectionSize; i++) {
MyHashMap fittest = copyPop.getFittest(i);
newPop.saveIndividual(i, fittest.getValue());
//將從i到最後種羣中適應值最大的個體與第i位置個體進行交換,利用冒泡排序思想,從大到小排序,i之前的總是按適應值排好的
copyPop.saveIndividual(fittest.getKey(), copyPop.getIndividual(i));
copyPop.saveIndividual(i, fittest.getValue());
}
return newPop;
}
/** 交叉操作包括單點、兩點和均勻交叉;在這裏實現了單點交叉 */
private static Population crossover(Population pop, double[] fitValue) {
//交叉種羣的數量
int crossSize = crossSize(pop);
//創建一個非初始化的種羣
Population crossPop = new Population(crossSize, false);
//設置競爭選擇隨機選擇的種羣大小
Util.setTournamentSize(10);
//存放根據適應值概率性選擇的兩個
Individual indiv1 = Util.tournamentSelection(pop);
Individual indiv2 = Util.tournamentSelection(pop);
//兩個臨時變量存放一對個體產生的兩個交叉後代
Individual temp1 = new Individual();
Individual temp2 = new Individual();
//循環選擇crossSize/2對的個體進行交叉;
int j = 0;
for (int i = 0; i < crossSize - 1; i = i + 2) {
//計算個體的編碼長度
int len = pop.getIndividual(0).size();
//實現多點交叉
for (int m = 0; m < len; m++) {
if (Math.random() <= UNIFORM_RATE) {
temp1.setGene(m, indiv1.getGene(m));
temp2.setGene(m, indiv2.getGene(m));
}
else {
temp1.setGene(m, indiv2.getGene(m));
temp2.setGene(m, indiv1.getGene(m));
}
}
//保存交叉得到的兩個後代到新種羣中
crossPop.saveIndividual(i, temp1);
crossPop.saveIndividual(i+1, temp2);
}
//<<Effective Java>>:消除廢棄的對象引用
temp1 = temp2 = indiv1 = indiv2 = null;
//判斷crossSize的奇偶性;若爲奇數,則根據個體適應值概率性的選擇一個個體到交叉種羣中
double rand = Math.random();
if (crossSize % 2 == 1)
while (j < pop.size())
if (rand < fitValue[j]) {
crossPop.saveIndividual(crossSize - 1, pop.getIndividual(j));
break;//跳出循環
}
else
j++;
return crossPop;
}
/**
* 突變操作;當種羣數量小於一定數量時, 突變的可能性爲0;所以這個實現版本只適用於種羣數量較大時的遺傳算法;否則
* 算法必收斂到局部最優解。改進版本的遺傳算法是自適應的遺傳算法,突變率隨着種羣進化的次數增加而增加,在確保收斂
* 穩定性的情況下,可以跳過局部最優解;盡力搜索全局最優。
*/
private static void mutate(Population pop) {
// 對種羣的中非精英個體進行突變操作
for (int i = selectSize(pop); i < pop.size(); i++) {
if (Math.random() < Algorithm.MUTATION_RATE) {
Individual indiv = pop.getIndividual(i);
//隨機選擇一個突變的位置
int randPos = (new Random()).nextInt(indiv.size());
//若突變的位置原始值爲1則反轉爲0,反之亦然;(《代碼大全》構建簡潔的代碼)
indiv.setGene(randPos, (byte)(1-indiv.getGene(randPos)));
}
}
}
/** 計算選擇種羣的大小 */
private static int selectSize(Population pop) {
return (int)Math.round((1-Algorithm.CROSSOVER_RATE) * pop.size());
}
/** 計算交叉種羣的大小 */
private static int crossSize(Population pop) {
return pop.size() - selectSize(pop);
}
}
Ecplise中運行的結果:
package simpleGa;
class EfficientAlgorithm implements Algorithm{
/** 保留精英布爾常量*/
private static final boolean ELITISM = true;
/**
* 《Effective Java》對於所有的可供其他類訪問的域、方法和類寫註釋文檔
* 此算法從種羣中選擇一個精英個體保留到下一代;從當前種羣中隨機選擇預設競爭種羣數量的個體到競爭種羣中,
* 從競爭種羣中選擇適應值最高的個體到作爲交叉個體,每一次交叉按照這種方法選擇兩個個體,使用多點均勻交叉
* 產生一個後代作爲下一代的種羣的個體;對新種羣進行變異操作,變異不包括精英。
* http://www.theprojectspot.com/tutorial-post/creating-a-genetic-algorithm-for-beginners/3
* @param pop:Poplation
* @return Population
*/
public Population evolvePopulation(Population pop) {
Population newPopulation = new Population(pop.size(), false);
int elitismoffset = 0;
//判斷是否保留當前種羣中具有最高適應度的個體———精英,到下一代種羣中
if (ELITISM) {
newPopulation.saveIndividual(0, pop.getFittest());
elitismoffset = 1;
}
else
elitismoffset = 0;
//循環使用交叉創建新個體
Individual indiv1, indiv2, newIndiv;
for (int i = elitismoffset; i < pop.size(); i++) {
indiv1 = Util.tournamentSelection(pop);
indiv2 = Util.tournamentSelection(pop);
newIndiv = crossover(indiv1, indiv2);
newPopulation.saveIndividual(i, newIndiv);
}
//除跨世代保留的精英外,對種羣中其他個體進行突變操作
for (int i = elitismoffset; i < newPopulation.size(); i++)
mutate(newPopulation.getIndividual(i));
return newPopulation;
}
/** 個體交叉,均勻交叉*/
private static Individual crossover(Individual indiv1, Individual indiv2) {
Individual newIndividual = new Individual();
for (int i = 0; i < indiv1.size(); i++) {
if (Math.random() <= Algorithm.CROSSOVER_RATE)
newIndividual.setGene(i, indiv1.getGene(i));
else
newIndividual.setGene(i, indiv2.getGene(i));
}
return newIndividual;
}
/** 突變一個個體 */
private static void mutate(Individual indiv) {
for (int i = 0; i < indiv.size(); i++) {
if (Math.random() <= Algorithm.MUTATION_RATE) {
byte gene = (byte)Math.round(Math.random());
indiv.setGene(i, gene);
}
}
}
}
Ecplise中運行的結果: