1. 程式人生 > >遺傳演算法詳解及Java實現

遺傳演算法詳解及Java實現

  1. 遺傳演算法的起源
    ==========

20世紀60年代中期,美國密西根大學的John Holland提出了位串編碼技術,這種編碼既適合於變異又適合雜交操作,並且他強調將雜交作為主要的遺傳操作。遺傳演算法的通用編碼技術及簡單有效的遺傳操作為其廣泛的應用和成功奠定了基礎。

  1. 遺傳演算法的目的
    ==========

解決經典數學方法無法有效地求出最優解的複雜的、大規模的難題。

  1. 遺傳演算法的思想
    ==========

遺傳演算法通常使用二進位制編碼來仿照基因編碼,初代種群產生之後,按照適者生存和優勝劣汰的原理,逐代(generation)演化產生出越來越好的近似解,在每一代,根據問題域中個體的適應度(fitness)

大小選擇個體,並藉助於自然遺傳學的遺傳運算元(genetic operators)進行組合交叉(crossover)變異(mutation),產生出代表新的解集的種群。

  1. 遺傳演算法的步驟
    ==========

 (1) 用固定長度的染色體表示問題變數域,選擇染色體種群數量為N,交叉概率為C,突變概率為M
 (2) 定義適應性函式來衡量問題域上單個染色體的效能或適應性。適應性函式是在繁殖過程中選擇配對染色體的基礎。
 (3) 隨機產生一個大小為N的染色體的種群。
 (4) 計算每個染色體的適應性。
 (5) 在當前種群中選擇一對染色體。雙親染色體被選擇的概率和其適應性有關。適應性高的染色體被選中的概率高於適應性低的染色體。


 (6) 通過執行遺傳操作——交叉和突變產生一對後代染色體。
 (7) 將後代染色體放入新種群中。
 (8) 重複步驟5,直到新染色體種群的大小等於初始種群的大小N為止。
 (9) 用新(後代)染色體種群取代初始(雙親)染色體種群。
 (10) 回到步驟4,重複這個過程直到滿足終止條件為止。

演算法步驟

  1. 演算法思路:
    ========

 (1) 變數作為實數,可以視為演化演算法的表現型形式。從表現型到基因型的對映稱為編碼。我們這裡採用二進位制編碼,將某個變數值代表的個體表示為一個{0,1}二進位制串。串長取決於求解的精度。

 (2) 用遺傳演算法解決函式優化問題,先隨機產生0和1填充10個46位基因數字串來建立染色體的初始種群,再通過自然選擇,交叉,變異等步驟得出下一代種群。

 (3) 迭代,再次執行步驟2直到滿足需求或達到迭代次數。

6.遺傳演算法解決函式優化問題

(1)問題描述

問題

(2)編碼與解碼

確定求解精度到6位小數,由於區間長度為6,必須將區間[0,6]分為$ 6× 10^6 $
等份。因為由$ 2^{22} <6×10^6 <2^{23} $ 得,使用23位的二進位制數來表示變數,故單個染色體需46位基因(以表示xy兩個變數)

示例:
例子

Java實現程式碼:

	/**
	 * 將染色體轉換成x,y變數的值
	 */
	private double[] calculatefitnessvalue(String str) {

		//二進位制數前23位為x的二進位制字串,後23位為y的二進位制字串
		int a = Integer.parseInt(str.substring(0, 23), 2);      
		int b = Integer.parseInt(str.substring(23, 46), 2);

		double x =  a * (6.0 - 0) / (Math.pow(2, 23) - 1);    //x的基因
		double y =  b * (6.0 - 0) / (Math.pow(2, 23) - 1);    //y的基因

		//需優化的函式
		double fitness = 3 - Math.sin(2 * x) * Math.sin(2 * x) 
				- Math.sin(2 * y) * Math.sin(2 * y);
		
		double[] returns = { x, y, fitness };
		return returns;

	}

(3)輪盤選擇

基本思想:個體被選中的概率與其適應度值成正比,即按由個體適應度值所決定的某個規則選擇將進入下一代的個體。(詳細解析

Java實現程式碼:

	/**
	 * 輪盤選擇
	 * 計算群體上每個個體的適應度值; 
	 * 按由個體適應度值所決定的某個規則選擇將進入下一代的個體;
	 */
	private void select() {
		double evals[] = new double[ChrNum]; // 所有染色體適應值
		double p[] = new double[ChrNum]; // 各染色體選擇概率
		double q[] = new double[ChrNum]; // 累計概率
		double F = 0; // 累計適應值總和
		for (int i = 0; i < ChrNum; i++) {
			evals[i] = calculatefitnessvalue(ipop[i])[2];
			if (evals[i] < bestfitness){  // 記錄下種群中的最小值,即最優解
				bestfitness = evals[i];
				bestgenerations = generation;
				beststr = ipop[i];
			}

			F = F + evals[i]; // 所有染色體適應值總和
		}
		//劃分區間,打造選擇所用的輪盤
		for (int i = 0; i < ChrNum; i++) {
			p[i] = evals[i] / F;
			if (i == 0)
				q[i] = p[i];
			else {
				q[i] = q[i - 1] + p[i];
			}
		}
		for (int i = 0; i < ChrNum; i++) {
			double r = Math.random();
			if (r <= q[0]) {
				ipop[i] = ipop[0];
			} else {
				for (int j = 1; j < ChrNum; j++) {
					if (r < q[j]) {
						ipop[i] = ipop[j];
						break; //確定區間後跳出迴圈
					}
				}
			}
		}
	}

(4)組合交叉(雜交)

單點雜交:設定雜交概率與雜交個體,按照斷裂點進行染色體雜交

示例:
  雜交前:a=<0101|0000>, b=<0111|1111>
  雜交後:a=<0101|1111>, b=<0111|0000>

Java實現程式碼:

	/**
	 * 交叉操作 交叉率為60%,平均為60%的染色體進行交叉
	 */
	private void cross() {
		String temp1, temp2;
		for (int i = 0; i < ChrNum; i++) {
			if (Math.random() < 0.60) {
				int pos = (int)(Math.random()*GENE)+1;     //pos位點前後二進位制串交叉
				temp1 = ipop[i].substring(0, pos) + ipop[(i + 1) % ChrNum].substring(pos); 
				temp2 = ipop[(i + 1) % ChrNum].substring(0, pos) + ipop[i].substring(pos);
				ipop[i] = temp1;
				ipop[(i + 1) % ChrNum] = temp2;
			}
		}
	}

(5)變異

基本思想:根據變異概率選擇變異位點,將二進位制位改變

Java實現程式碼:

	/**
	 * 基因突變操作 1%基因變異
	 */
	private void mutation() {
		for (int i = 0; i < 4; i++) {
			int num = (int) (Math.random() * GENE * ChrNum + 1);
			int chromosomeNum = (int) (num / GENE) + 1; // 染色體號

			int mutationNum = num - (chromosomeNum - 1) * GENE; // 基因號
			if (mutationNum == 0) 
				mutationNum = 1;
			chromosomeNum = chromosomeNum - 1;
			if (chromosomeNum >= ChrNum)
				chromosomeNum = 9;
			String temp;
			String a;   //記錄變異位點變異後的編碼
			if (ipop[chromosomeNum].charAt(mutationNum - 1) == '0') {    //當變異位點為0時
                a = "1";
			} else {   
				a = "0";
			}
			//當變異位點在首、中段和尾時的突變情況
			if (mutationNum == 1) {
				temp = a + ipop[chromosomeNum].substring(mutationNum);
			} else {
				if (mutationNum != GENE) {
					temp = ipop[chromosomeNum].substring(0, mutationNum -1) + a 
							+ ipop[chromosomeNum].substring(mutationNum);
				} else {
					temp = ipop[chromosomeNum].substring(0, mutationNum - 1) + a;
				}
			}
			//記錄下變異後的染色體		
			ipop[chromosomeNum] = temp;
		}
	}

(6)執行結果

	public static void main(String args[]) {

		GA Tryer = new GA();
		Tryer.ipop = Tryer.initPop(); //產生初始種群
		String str = "";
		
		//迭代次數
		for (int i = 0; i < 100000; i++) {
			Tryer.select();
			Tryer.cross();
			Tryer.mutation();
			Tryer.generation = i;
		}
		
		double[] x = Tryer.calculatefitnessvalue(Tryer.beststr);

		str = "最小值" + Tryer.bestfitness + '\n' + "第" 
		        + Tryer.bestgenerations + "個染色體:<" + Tryer.beststr + ">" + '\n' 
				+ "x=" + x[0] + '\n' + "y=" + x[1];

		System.out.println(str);

	}

執行結果:
結果

完整程式碼:Github



參考資料:

  1. 《人工智慧——智慧系統指南》