什麼是遺傳算法
如果你去百度百科搜的話,他會給你講一堆遺傳算法的概念,其實說白了,遺傳算法就是計算機科學家從自然界獲得靈感總結的一套優化方法的總稱。
遺傳算法是用來解決優化問題的。很早之前,我看到過一個視頻,介紹使用遺傳算法來讓計算機設計能走的很遠的蛋白質結構。emmm,可能你會不知道我在說什麼,那就去看看這個視頻吧,這個視頻介紹的是用遺傳算法來設計小車的結構。
【boxcar2D】進化小車三個多小時的進化過程
遺傳算法相比於其他優化算法的優勢在於,他是一種弱方法,所謂弱方法就是說在各個領域幾乎都能用,並且你在優化的時候不需要過多的領域知識。並且,有的時候使用遺傳算法可以設計出超越人類知識邊界的模式
上圖就是NASA使用遺傳算法設計出的天線,是不是感覺很厲害。
遺傳算法的基本流程
遺傳算法的常用術語
搜索空間:在處理優化問題的時候我們通常是要搜索解空間,以便尋找最優解,所有的解構成的集合就稱爲搜索空間,我們舉一個非常非常簡單的模型。
上圖就是優化一個參數的例子,我們假設這個參數是連續的,那麼解空間就是無限大,我們需要找到一個適應度值最大的解作爲我們算法的結果。
使用遺傳算法搜索解空間通常都是由於解空間太大了,逐個遍歷不切實際。
種羣規模:種羣規模就是遺傳算法中任意一個種羣的個體數。在設計遺傳算法的時候我們通常需要找到一個平衡,因爲太多的大部分算法在個體數達到某個值的時候可以取得效果和效率上的平衡,超過這個值算法的效率就會降低,與此同時搜索的效果可能並不會變好。
交叉率:交叉率指的是在執行交叉方法的時候,兩個親代交換基因的概率。
變異率:字面意思,就是在進化過程中基因變異的概率。
基因表示:這可以說是遺傳算法設計中的核心問題,不同的問題應該使用不同的編碼,這樣才能使算法效率最大化。
精英主義:一個種羣中通常都會有適應度比較好的個體,如果在這些個體在進化的過程中也進行交叉和變異,那麼好的性狀可能得不到保留,換一種說法就是,算法就不收斂了。所以通常我們需要保護這些個體,讓他們的基因能夠安全地流傳下去。
實現一個簡單的遺傳算法
終於到了最激動人心的實現環節了,看了剛剛的介紹,你是不是也躍躍欲試了呢?下面我給大家介紹使用java編寫的簡單的遺傳算法,爲什麼用java?因爲java真的很好用
算法設計的目標:我們使用0和1來編碼基因組,目的是找到一個全爲1的基因編碼。
算法僞代碼
generation=0;
//初始化種羣
population[generation]=initializePopulation(populationSize);
//評估種羣的適應度
evaluatePopulation(population[generation])
while isTerminationConditionMet()==false do
//選擇親代
parents=selectParent(population[generation]);
//交叉
population[generation+1]=crossover(parents);
//變異
population[generation+1]=mutate(population[generation+1]);
//評估種羣適應度
evaluatePopulation(population[generation]);
//代數加一
generation++;
End Loop;
代碼示例
我們的代碼將包含你下面的四個類
GeneticAlgorithm:所有遺傳算法用到的算法都寫在這裏面
Individual:個體的屬性和方法
Population:羣體的屬性和方法,裏面集成了很多個體
AllOnesGA:類似於main函數
首先是Individual.java
package SimpleGA;
//這是個體類
public class Individual {
private int[] chromosome; //染色體數組
private double fitness = -1; //適應度
//含有染色體數組的構造函數
public Individual(int[] chromosome){
this.chromosome = chromosome;
}
//含有染色體長度的構造函數,隨機初始化染色體
public Individual(int chromosomeLength){
this.chromosome = new int[chromosomeLength];
for(int gene = 0; gene < chromosomeLength; gene++){
if(Math.random() > 0.5){
this.setGene(gene, 1);
} else {
this.setGene(gene, 0);
}
}
}
public int[] getChromosome(){
return this.chromosome;
}
public int getChromosomeLength(){
return this.chromosome.length;
}
public void setGene(int offset, int gene){
this.chromosome[offset] = gene;
}
public int getGene(int offset){
return this.chromosome[offset];
}
public void setFitness(double fitness){
this.fitness = fitness;
}
public double getFitness(){
return this.fitness;
}
public String toString(){
String output = "";
for(int gene = 0; gene < this.chromosome.length; gene++){
output += this.chromosome[gene];
}
return output;
}
}
接着是Population.java
package SimpleGA;
import java.util.Arrays;
import java.util.Comparator;
import java.util.Random;
//羣體類
public class Population {
private Individual population[];
private double populationFitness = -1; //羣體的適應度
public Population(int populationSize){
this.population = new Individual[populationSize];
}
public Population(int populationSize, int chromosomeLength){
this.population = new Individual[populationSize];
for(int individualCount = 0; individualCount < populationSize; individualCount++){
Individual individual = new Individual(chromosomeLength); //這通過長度創建個體
this.population[individualCount] = individual; //加入到羣體中
}
}
public Individual[] getIndividuals(){
return this.population;
}
//將個體按照適應度排序,適應度越高索引值越低
//排序後根據索引值獲得個體
public Individual getFittest(int offset){
Arrays.sort(this.population, new Comparator<Individual>() {
@Override
public int compare(Individual o1, Individual o2) {
if(o1.getFitness() > o2.getFitness()){
return -1; //前面比後面高就返回-1
} else if(o1.getFitness() < o2.getFitness()){
return 1; //前天比後面第就返會1,此時交換兩個元素
} else {
return 0; //相等返回0
}
}
});
return this.population[offset];
}
public void setPopulationFitness(double fitness){
this.populationFitness = fitness;
}
public double getPopulationFitness(){
return this.populationFitness;
}
//得到羣體中的個體數
public int size(){
return this.population.length;
}
//將個體加入到羣體中的某個位置
public Individual setIndividual(int offset, Individual individual){
return population[offset] = individual;
}
public Individual getIndividual(int offset){
return this.population[offset];
}
//對於每一個個體,都將它與其他任意個體隨機交換
public void shuffle(){
Random rnd = new Random();
for(int i=population.length-1; i>0; i--){
int index = rnd.nextInt(i+1); //隨機獲得[0, i+1)的一個數
Individual a = population[index]; //進行交換
population[index] = population[i];
population[i] = a;
}
}
}
然後是GeneticAlgorithm.java
package SimpleGA;
//這個類寫遺傳算法的通用算法
public class GeneticAlgorithm {
private int populationSize; //搜索範圍
private double mutationRate; //變異率
private double crossoverRate; //交叉率
private int elitismCount; //精英數量
//構造函數
public GeneticAlgorithm(int populationSize, double mutationRate, double crossoverRate, int elitismCount) {
this.populationSize = populationSize;
this.mutationRate = mutationRate;
this.crossoverRate = crossoverRate;
this.elitismCount = elitismCount;
}
//初始化羣體
public Population initPopulation(int chromosomeLength){
Population population = new Population(this.populationSize, chromosomeLength);
return population;
}
//評估適應度
public double calcFitness(Individual individual){
//記錄爲1的染色體數量
int correctGenes = 0;
//掃描所有染色體
for(int geneIndex =0; geneIndex<individual.getChromosomeLength(); geneIndex++){
if(individual.getGene(geneIndex)==1){
correctGenes++;
}
}
//適應度爲二進制編碼中爲1的編碼佔編碼總數的比例
double fitness = (double)correctGenes / individual.getChromosomeLength();
//存儲適應度
individual.setFitness(fitness);
return fitness;
}
//評估羣體適應度,其值就是每個個體適應度加和
public void evalPopulation(Population population){
double populationFitness = 0; //羣體的適應度
for(Individual individual : population.getIndividuals()){
populationFitness += calcFitness(individual);
}
population.setPopulationFitness(populationFitness);
}
//終止檢查
public boolean isTerminationConditionMet(Population population){
//只要羣體中有一個個體的適應度爲1就終止檢查
for(Individual individual : population.getIndividuals()){
if(individual.getFitness()==1){
return true;
}
}
return false;
}
//選擇方法
//這個方法比較難理解,有點像概率裏面的分佈函數
//我們使用輪盤賭的方式實現交叉,就是古典概型,適應度高的個體被選中染色體的概率就高
public Individual selectParent(Population population){
//獲得個體
Individual individuals[] = population.getIndividuals();
double populationFitness = population.getPopulationFitness();
double rouletteWheelPosition = Math.random()*populationFitness;
//Math.random() 返回[0, 1)的隨機數
//找到親代
double spinWheel = 0;
for(Individual individual: individuals){
spinWheel += individual.getFitness();
if(spinWheel >= rouletteWheelPosition){
return individual;
}
}
return individuals[population.size()-1]; //返回羣體中的最後一個個體
}
//交叉方法
public Population crossoverPopulation(Population population){
//創建新種羣
Population newPopulation = new Population(population.size());
//根據種羣的適應度遍歷種羣
for(int populationIndex=0; populationIndex<population.size(); populationIndex++){
Individual parent1 = population.getFittest(populationIndex);
//如果概率上達到了交叉的條件,並且不是精英就交叉(精英的性狀直接保留至下一代)
if(this.crossoverRate>Math.random() && populationIndex>this.elitismCount){
//初始化後代
Individual offspring = new Individual(parent1.getChromosomeLength());
//找到第二個親代
//形狀越好找被選中的概率越大
Individual parent2 = selectParent(population);
//開始交叉操作
for(int geneIndex=0; geneIndex<parent1.getChromosomeLength(); geneIndex++){
//交叉的時候有一半的概率使用p1的基因,一半的概率使用p2的基因
if(Math.random() < 0.5){ //使用p1的基因
offspring.setGene(geneIndex, parent1.getGene(geneIndex));
} else { //使用p2的基因
offspring.setGene(geneIndex, parent2.getGene(geneIndex));
}
}
//將子代加入到種羣中,實際上就是將原來的parent1替換成offspring了
newPopulation.setIndividual(populationIndex, offspring);
} else {
//直接將個體添加到種羣中
newPopulation.setIndividual(populationIndex, parent1);
}
}
return newPopulation;
}
public Population mutatePopulation(Population population){
//初始化新種羣
Population newPopulation = new Population(this.populationSize);
//根據適應度遍歷種羣
for(int populationIndex=0; populationIndex<population.size(); populationIndex++){
Individual individual = population.getFittest(populationIndex);
//遍歷個體的基因
for(int geneIndex=0; geneIndex<individual.getChromosomeLength(); geneIndex++){
//精英就不用變異了(富人靠科技,窮人靠變異)
if(populationIndex >= this.elitismCount){
if(this.mutationRate > Math.random()){ //達到了變異的條件
int newGene = 1;
if(individual.getGene(geneIndex) == 1){
newGene = 0; //既然是變異,那麼就應該在相應位置將基因取反
}
//將變異後的基因插入
individual.setGene(geneIndex, newGene);
}
}
}
//添加個體到羣體中
newPopulation.setIndividual(populationIndex, individual);
}
//返回變異後的羣體
return newPopulation;
}
}
最後是AllOnesGA.java
package SimpleGA;
//引導類,用於初始化遺傳算法
public class AllOnesGA {
public static void main(String[] args){
//實例化一個遺傳算法對象
GeneticAlgorithm ga = new GeneticAlgorithm(100, 0.01, 0.95, 3);
//初始化羣體,染色體長度爲50
Population population = ga.initPopulation(50);
//下面進行進化計算
ga.evalPopulation(population);
int generation = 1; //進化的代數
while(ga.isTerminationConditionMet(population) == false){
//打印羣體中適應度最高的個體
System.out.println("Best solution: "+population.getFittest(0).toString() +" generation: "+generation);
//交叉
//更新種羣
population = ga.crossoverPopulation(population);
//變異
population = ga.mutatePopulation(population);
//評估羣體
ga.evalPopulation(population);
//代數加一
generation++;
}
//打印最終結果
System.out.println("Found solution in " + generation + "generations");
System.out.println("Best solution: "+population.getFittest(0).toString());
}
}
你可以在設置不同的初始值來計算,你會發現,幾乎每一次計算的結果都不一樣,並且如果精英數設置爲0的話,算法可能就不收斂了,我使用上面代碼的參數跑的結果如下
總結
這只是遺傳算法一個很簡單的示例,遺傳算法還可以解決很多很複雜的問題,總的來說還是挺有意思的哈哈。