4 h/ f" A% J& y2 J! [8 O' k优化算法入门系列文章目录(更新中):
遗传算法 ( GA , Genetic Algorithm ) ,也称进化算法 。 遗传算法是受达尔文的进化论的启发,借鉴生物进化过程而提出的一种启发式搜索算法。因此在介绍遗传算法前有必要简单的介绍生物进化知识。
8 f, s+ p& O+ e* V# p; L9 E0 K
1 E& w; T. [. O一.进化论知识 作为遗传算法生物背景的介绍,下面内容了解即可:
种群(Population):生物的进化以群体的形式进行,这样的一个群体称为种群。
个体:组成种群的单个生物。
基因 ( Gene ) :一个遗传因子。
染色体 ( Chromosome ) :包含一组的基因。
生存竞争,适者生存:对环境适应度高的、牛B的个体参与繁殖的机会比较多,后代就会越来越多。适应度低的个体参与繁殖的机会比较少,后代就会越来越少。
遗传与变异:新个体会遗传父母双方各一部分的基因,同时有一定的概率发生基因变异。
. q9 @+ o3 q, Q3 i
简单说来就是:繁殖过程,会发生基因交叉( Crossover ) ,基因突变 ( Mutation ) ,适应度( Fitness )低的个体会被逐步淘汰,而适应度高的个体会越来越多。那么经过N代的自然选择后,保存下来的个体都是适应度很高的,其中很可能包含史上产生的适应度最高的那个个体。
" F: v u- j2 F* [0 I4 t6 j+ l+ F8 [. K7 r7 ~
二.遗传算法思想 借鉴生物进化论,遗传算法将要解决的问题模拟成一个生物进化的过程,通过复制、交叉、突变等操作产生下一代的解,并逐步淘汰掉适应度函数值低的解,增加适应度函数值高的解。这样进化N代后就很有可能会进化出适应度函数值很高的个体。
举个例子,使用遗传算法解决“0-1背包问题”的思路:0-1背包的解可以编码为一串0-1字符串(0:不取,1:取) ;首先,随机产生M个0-1字符串,然后评价这些0-1字符串作为0-1背包问题的解的优劣;然后,随机选择一些字符串通过交叉、突变等操作产生下一代的M个字符串,而且较优的解被选中的概率要比较高。这样经过G代的进化后就可能会产生出0-1背包问题的一个“近似最优解”。
& a& D Z, Z8 f' G2 \, `; F2 c
编码:需要将问题的解编码成字符串的形式才能使用遗传算法。最简单的一种编码方式是二进制编码,即将问题的解编码成二进制位数组的形式。例如,问题的解是整数,那么可以将其编码成二进制位数组的形式。将0-1字符串作为0-1背包问题的解就属于二进制编码。
/ O- S+ y* T2 V' D8 Q" n% N% I% @
遗传算法有3个最基本的操作:选择,交叉,变异。
: S' j7 D+ C6 ?! I& [& s" i 选择:选择一些染色体来产生下一代。一种常用的选择策略是 “比例选择”,也就是个体被选中的概率与其适应度函数值成正比。假设群体的个体总数是M,那么那么一个体Xi被选中的概率为f(Xi)/( f(X1) + f(X2) + …….. + f(Xn) ) 。比例选择实现算法就是所谓的“轮盘赌算法”( Roulette Wheel Selection ) ,轮盘赌算法的一个简单的实现如下:
5 f+ C! O0 t$ y+ l2 z8 f/ l8 j3 l; R6 p[url=]
[/url]- t( I, { } P, p% B4 Y, l
轮盘赌算法/*1 U: b# N7 i% b. m% G9 d) U
* 按设定的概率,随机选中一个个体
1 H. }* v+ Q4 n" P, `7 h* P表示第i个个体被选中的概率$ l0 Q M, z' m( _, z
*/
$ y' d& l! i G" ~& [int RWS()9 p+ a- M. k3 q8 e) K+ p. g S
{
( m) r+ M' [/ i$ fm =0;# |& {8 J% Q8 K9 q8 u
r =Random(0,1); //r为0至1的随机数
- B5 ~' n9 j# vfor(i=1;i<=N; i++)
: T. E9 ]! O2 C6 G" ^2 k0 [" i( Z{
) X$ s" u6 b" M6 p4 g$ `; ^# L/* 产生的随机数在m~m+P间则认为选中了i
8 A9 l, [- G/ M V& W% P" c1 V* 因此i被选中的概率是P, z% e% O7 [# _5 c
*/& q; c3 {8 t/ i8 I
m = m + P;# x# _% }% p2 d4 y
if(r<=m) return i;
g, R& M; v- Y- V! Q}
6 V1 z( j. S$ s' r}
2 Z1 x( X! j$ Y: r. u/ n; M+ \. r/ w0 I7 d7 R/ K, c S
[url=]
[/url]9 T; H5 f5 S& c [7 K# j$ r2 S, E* L
" R! x+ g4 n9 R9 F' b$ @
4 U* S( v1 D; z& `8 K- E% y6 K
交叉(Crossover):2条染色体交换部分基因,来构造下一代的2条新的染色体。例如:
交叉前:
00000|011100000000|10000
11100|000001111110|00101
交叉后:
00000|000001111110|10000
11100|011100000000|00101
染色体交叉是以一定的概率发生的,这个概率记为Pc 。
- L( q; p& O( Z% m+ w. J' [+ A2 t变异(Mutation):在繁殖过程,新产生的染色体中的基因会以一定的概率出错,称为变异。变异发生的概率记为Pm 。例如:
变异前:
000001110000000010000
变异后:
000001110000100010000
适应度函数 ( Fitness Function ):用于评价某个染色体的适应度,用f(x)表示。有时需要区分染色体的适应度函数与问题的目标函数。例如:0-1背包问题的目标函数是所取得物品价值,但将物品价值作为染色体的适应度函数可能并不一定适合。适应度函数与目标函数是正相关的,可对目标函数作一些变形来得到适应度函数。
" N# p% K2 h( J6 {% c5 s7 J. n' P; D) U# B9 V- x! D; l5 ]8 a
三.基本遗传算法的伪代码1 A4 u7 g6 W4 |" U# k F( A* J2 v. `' r
. h1 ?' t* |+ k8 V
[url=]
[/url]- |% [: e& E' j0 N0 f/ P( _- F
基本遗传算法伪代码/*# s: ~% h# x1 r* D/ L
* Pc:交叉发生的概率
3 ?8 C5 b5 p* i* Pm:变异发生的概率
. f/ v: H ]3 D" e0 j' K2 g& r* M:种群规模" R- c S s2 i6 @6 L" E9 ^) n
* G:终止进化的代数
$ P" q2 q+ g& l. S* Tf:进化产生的任何一个个体的适应度函数超过Tf,则可以终止进化过程
z- q y- x4 o0 t# V! B: q*/8 R8 n$ w h p! z5 d8 l# i
初始化Pm,Pc,M,G,Tf等参数。随机产生第一代种群Pop6 U% }$ k) B: }6 k7 E
& S% _. q( o, [. |0 F
do
; O% P1 H h: [, ?3 o" Y* b{ 7 w" k3 e! k: P' y7 K& q
计算种群Pop中每一个体的适应度F(i)。
, X, d" L9 S6 v& N* h. s 初始化空种群newPop
t" n' q6 c- z. M+ R5 r do
2 l B, d/ W' D% s3 f {0 m% t6 G7 g- E2 l3 |
根据适应度以比例选择算法从种群Pop中选出2个个体' Z+ n8 W" ?& s2 J4 B
if ( random ( 0 , 1 ) < Pc )
; x, ]* K2 b9 w9 ~8 W {
: L. G. P, u# @6 ^, x2 v 对2个个体按交叉概率Pc执行交叉操作: q9 \* ?' T) H
}4 ~* W3 A0 ]8 j0 x7 _8 q9 b
if ( random ( 0 , 1 ) < Pm )
" j2 B6 Q5 W. x" Y7 m- E {* I2 a1 X+ u: x* |' Y5 `
对2个个体按变异概率Pm执行变异操作
( ~1 ?: M; D( d( k5 x5 f+ X5 A' s }
9 S8 }$ J. Y2 T8 ?: P9 }将2个新个体加入种群newPop中
9 T; i1 r9 o8 q# D. g" B} until ( M个子代被创建 )
5 W3 p, `+ n( R0 V# I用newPop取代Pop
! `5 s) x8 x! x7 K# A}until ( 任何染色体得分超过Tf, 或繁殖代数超过G )
, F2 p& ?$ L& \2 w" _ & m& |* T4 A! T9 ` P* `5 |
, P D. D# G( f8 Q3 l! e* Y
[url=]
[/url]
3 G5 e1 |/ H+ _" k: q; R! T4 v. ~# `% \
& z4 q, \/ E" Q* E
$ u9 \, s9 `8 C! G7 @2 H+ Y* \四.基本遗传算法优化 下面的方法可优化遗传算法的性能。
精英主义(Elitist Strategy)选择:是基本遗传算法的一种优化。为了防止进化过程中产生的最优解被交叉和变异所破坏,可以将每一代中的最优解原封不动的复制到下一代中。
插入操作:可在3个基本操作的基础上增加一个插入操作。插入操作将染色体中的某个随机的片段移位到另一个随机的位置。
五. 使用AForge.Genetic解决TSP问题 AForge.NET是一个C#实现的面向人工智能、计算机视觉等领域的开源架构。AForge.NET中包含有一个遗传算法的类库。
: b" Y( P( D/ q* K5 j2 \
: G. j2 ?% _% s @
介绍一下AForge的遗传算法用法吧。AForge.Genetic的类结构如下:
. w8 J8 n4 P) p% E7 c9 m
图1. AForge.Genetic的类图
; l( O7 n/ J4 B7 F7 ]+ A2 Z0 b. {4 G
下面用AForge.Genetic写个解决TSP问题的最简单实例。测试数据集采用网上流传的中国31个省会城市的坐标:
1 |) r8 a: R2 _- n$ F1 G J5 c
[url=]
[/url]4 A/ ~' I6 v. a* J) O4 A' n& b
13042312( b( _1 o. `5 d$ T* t* n D
363913157 f" b; |. x0 s4 e# S
41772244; {; `+ n L8 `. y9 K, h
37121399! L8 }/ k5 }, `. l, p) R
34881535
/ }% x7 b- q/ @4 _" V$ L3 ^33261556
. M3 T3 ^- n' i8 T2 V* {% p32381229
# X( t7 K! P6 j# i& l4 x; L41961004
5 @$ T2 k3 W# a/ ?4312790; O7 f/ G5 G: J- c% r/ k
4386570
8 r- |8 f5 i/ R8 G300719707 w# `/ u N+ V" o0 X
25621756' n( Q% J2 K& w. f' p' l
278814915 Y$ Y& ?5 b* T
23811676! N5 k, t% p4 ^/ ]: s9 f+ i
1332695
; u( D2 B, k% @1 ]2 o37151678
/ o+ p( s2 g8 g* @- R, E6 l! R39182179
9 K; m8 Y6 U$ V! F" B$ M$ j9 A; _: v" p5 d40612370 w0 \, E: b7 e* B, A
37802212
0 C3 o! y4 L5 y1 p( g36762578
9 V( T$ D _. k40292838
" N1 q9 C* z3 Y$ f( r9 s2 v5 E, l; c% H42632931
9 r& U$ x1 p+ ~34291908% {, T! ?2 k) e) a, x* Z" t
35072367
. A9 }9 M6 `( g) c: K; w9 a33942643
' [! o+ s( d3 z$ k6 N+ S34393201
2 o5 M/ [! m7 Z29353240
/ G8 e4 t, Y B w" B- \ t314035501 F: W/ S7 X3 `0 U: Z7 k9 |4 ~6 X
25452357
/ Z7 L; v" W; w27782826* q) X5 f1 E& ]! j% [
237029751 P& }% Q( B/ b4 L X/ I" a; j
[url=]
[/url]
! V+ o' E$ x; h% d4 t
5 h3 x, ^4 q, H' `$ Y4 Y6 v% f9 q- s8 c
' Z) O" A8 W& f- t5 p
5 S' N [5 }$ }' a' V+ o A0 x) `
操作过程:
(2) 创建C#空项目GenticTSP。然后在AForge目录下找到AForge.dll和AForge.Genetic.dll,将其拷贝到TestTSP项目的bin/Debug目录下。再通过“Add Reference...”将这两个DLL添加到工程。
(3) 将31个城市坐标数据保存为bin/Debug/Data.txt 。
(4) 添加TSPFitnessFunction.cs,加入如下代码:
2 b$ {' o' K* O) x% i7 A$ H[url=]
[/url]
) r( ]4 _% Y, @% o: ~ P- ^
TSPFitnessFunction类using System;
! h, U# d, p3 c$ E3 qusing AForge.Genetic;
+ t( v E" k: |$ U& x& a2 r' `7 b5 z7 B) Z
namespace GenticTSP
% C& u+ }& X0 v/ y! z$ r9 E% s$ P{0 Z1 g; s+ [5 s/ Y8 v( `) Q
///<summary>! | K8 H* D& B/ \+ \7 j* W
/// Fitness function for TSP task (Travaling Salasman Problem)
4 p: `0 d( [& h. R///</summary>
3 b9 p8 V$ t" j" mpublicclass TSPFitnessFunction : IFitnessFunction2 F' f- A9 g: j! u. P
{+ t; X# `. |0 H1 j M8 t
// map8 ]) q8 v7 R1 P8 s
privateint[,] map =null;7 f% v0 p4 p: _. e
% S n: i1 M, R+ e' d1 l {# j// Constructor6 a6 O$ Q- h9 }7 M9 D, k: k" ?
public TSPFitnessFunction(int[,] map)' v i. k1 [, n
{
) Y- X* f: b0 u9 O, ^. Jthis.map = map;2 M6 |( z+ P. f. `1 a% x
}
/ R3 s m7 N2 i \: V3 v' F2 h8 S, S
///<summary>$ W# E: M& ^: I7 s" n1 R1 s* `" X
/// Evaluate chromosome - calculates its fitness value
! Q' Q) v2 e- [& |0 A1 h///</summary>
; p8 a0 ^/ F: P; h. u5 apublicdouble Evaluate(IChromosome chromosome)+ X6 u! b* w+ G8 ^# ^
{( I _( w5 s9 [! c
return1/ (PathLength(chromosome) +1);* ?% Q7 R) a6 u: y5 J! X
}
) g7 A( p6 `; T; V4 K7 [& |1 U5 n: @8 i5 r: P' t, a* c
///<summary>- N6 m5 D" m; R
/// Translate genotype to phenotype
/ T! ]( Q# B$ K' m///</summary>
E( x) f. S0 Fpublicobject Translate(IChromosome chromosome)
8 v0 _" j! U9 m2 {4 Z8 e{
8 J% |( l+ ?. B, xreturn chromosome.ToString();
- M; N, y. N1 m( ~1 w7 O}. g5 I+ S; g! H$ m) j3 B2 @: e
8 ^/ v1 d) I0 H# Z) G% j6 a
///<summary>. v" W3 l0 Y- M1 }2 c2 H
/// Calculate path length represented by the specified chromosome
4 }, L7 s, B% N5 d1 _% R: w///</summary>
9 A' [; J7 F; p: Tpublicdouble PathLength(IChromosome chromosome)& A+ L- z' B$ f5 M: z5 R& a9 r
{
: x. ^9 J* k" x// salesman path
1 ~% m8 B) c2 J' t( u4 g C3 Tushort[] path = ((PermutationChromosome)chromosome).Value;
# o3 V) U7 P9 b) O# @
( x( w$ m8 m5 K// check path size
, C5 z$ S' U _if (path.Length != map.GetLength(0))
% `) g5 ^, e7 o{% m" @* R# c& n( k! g
thrownew ArgumentException("Invalid path specified - not all cities are visited");
1 Q F/ ~& |2 V/ Z8 T! ^( O}5 R! _, Z9 Z; z: _* h8 R
7 `& V% y' z6 [1 l7 I// path length4 [7 \( p7 d$ {
int prev = path[0];
1 `; N+ S5 Q7 Z7 b; uint curr = path[path.Length -1];
: @/ i( d, B2 `
8 Q% d) ?& V" C: I: h: t& U! x// calculate distance between the last and the first city
' S( q# b: [4 `double dx = map[curr, 0] - map[prev, 0];0 j: N: G* ^2 g7 e/ O
double dy = map[curr, 1] - map[prev, 1];
# N8 k; U5 A4 Q, g% Z% Y: Wdouble pathLength = Math.Sqrt(dx * dx + dy * dy);
9 V) w1 c: c" i3 N2 t, Z$ v1 Z. j6 K! v- t/ l# @
// calculate the path length from the first city to the last0 d- E' o) ^" i& s0 ^9 p" E
for (int i =1, n = path.Length; i < n; i++)$ P- {! C( W; I+ @
{/ h$ Q2 d; J0 p6 g8 B5 }
// get current city5 l2 [/ z6 S- V" @
curr = path;* O. P; u/ {9 w' J( i" T7 i; u* |5 j! W
( l: w m+ s$ B8 R$ w6 q
// calculate distance: s; @& v( w: X
dx = map[curr, 0] - map[prev, 0]; p' ]9 L8 {, ^/ H. c; y" y, h S
dy = map[curr, 1] - map[prev, 1];
( w/ l% a; u; T7 B5 H; V# h# e; BpathLength += Math.Sqrt(dx * dx + dy * dy);
# q+ h: z# e, r s# p' }% k9 V* m8 z5 K" i9 \# g! \+ T: u2 S0 }7 z
// put current city as previous8 s; T3 Q4 \ L5 t' |7 H
prev = curr;9 o+ _6 _( n2 R1 m
}
' e5 E: {* s* g" _& q. E" l2 q1 x# ]9 m- V
return pathLength;
: t0 D7 z3 R. F* Z; m: {# C2 i}
4 J1 I# \ q& J1 t} F3 W u5 k6 P( \3 T7 {8 o% T
}6 w, E, x" I8 _, y) T; [
9 L4 g& z" Y" g# O" a" ^( n1 R- F
# S" \+ D7 e" S; {) h+ e! r
[url=]
[/url]- ^/ d! A w7 u5 d
2 `% `7 g! h% X5 E4 m
5 k" j6 r, x/ ?% K; [: J+ }; V/ Z0 a5 G$ [" b7 j8 F. n
(5) 添加GenticTSP.cs,加入如下代码:
g; ], Y( [- X[url=]
[/url] T" `, @- L% Z- W R9 E9 z6 L
GenticTSP类using System;! Q0 M+ X% y0 k. R. l
using System.Collections.Generic;8 N" s* V) k5 Y' W$ o o
using System.Linq;
6 G7 A. r# ?/ K5 ~using System.Text;4 F a) @7 C+ j) o9 E
using System.IO;
; |9 @6 D$ t( U& s9 o( _+ d, U9 h3 U
( B2 r' I" ?9 D ?: Yusing AForge;
) ^8 n( c8 q/ b! C4 `" }$ ~/ _using AForge.Genetic;- `9 q5 i5 x+ m
6 C) ]8 v }( {# S! z& k
) g! h& h# Y' D& @& Znamespace GenticTSP
5 E' a; p0 F8 L) t% b' D{# L+ d- @ x& G" q. S) d
class GenticTSP' `) l( P) ^/ h6 q/ b
{! f- J7 q& d: v* W
, o+ J+ @$ E9 M: x$ [) Qstaticvoid Main()
2 C t" L3 o |( u{0 g' Y0 s# W, h! |
StreamReader reader =new StreamReader("Data.txt");9 q8 l2 i: G3 \
( T6 d7 J7 j! u+ j X2 c, {
int citiesCount =31; //城市数
8 T5 m8 {5 G8 t# {, c
* r" I) [3 k: Z ~* V, W* ~( }& \int[,] map =newint[citiesCount, 2];0 N2 {/ v- y# \; F
, F' Q$ Y0 a1 tfor (int i =0; i < citiesCount; i++)- \+ A) R+ a0 t8 E- l
{( ?/ e; n: @/ l) h* h. h$ ?
string value = reader.ReadLine();' B0 h7 N/ i% _* r$ ?9 h
string[] temp = value.Split('');
* d' V8 t5 [1 Rmap[i, 0] =int.Parse(temp[0]); //读取城市坐标
+ d) B/ o# D" E: X* U9 D' ]map[i, 1] =int.Parse(temp[1]);5 ^+ u @) O: ]) F" [# i% ]
}
0 X6 K+ z/ u6 P9 o; `( L
, {( A# q$ @. r& z// create fitness function
# h5 \* A% P- U4 a) zTSPFitnessFunction fitnessFunction =new TSPFitnessFunction(map);
; T3 k& t4 j; I( B7 B! L* J! M9 N: @; V& m9 k' \! \4 S
int populationSize = 1000; //种群最大规模% O& |6 c1 u0 I5 N% W& d
* D* o- m( R. C$ K. Y# e8 p$ t a/* " L" P X, p, J- e. D. M
* 0:EliteSelection算法 " d: b8 P' ^) e0 ]/ h
* 1:RankSelection算法
H+ H8 ~6 f2 o* 其他:RouletteWheelSelection 算法& d0 ^9 f+ h" q# {$ e6 X$ r
* */
2 g; K+ M; x2 ?1 Oint selectionMethod =0;9 p9 {; D" n2 i; |( `$ Y. G5 c/ P
0 `' q' P2 ~& y# o7 u% y9 S
// create population- N/ v) c# e) e4 {! A3 c# N$ `1 C' N
Population population =new Population(populationSize,
0 T: `- A6 j* ^new PermutationChromosome(citiesCount),
* U- B; o' U9 `" X; e' @+ NfitnessFunction,
+ G) b/ G: Q, D1 d' v0 w$ U# U0 ](selectionMethod ==0) ? (ISelectionMethod)new EliteSelection() :
1 b5 _! B( k# q0 c(selectionMethod ==1) ? (ISelectionMethod)new RankSelection() :
4 z/ j( h3 i* J8 a(ISelectionMethod)new RouletteWheelSelection()
" d0 a* _4 P) o. I' ~) `, H1 r* `);
5 ]7 V6 p5 G; t( b+ Y* u5 T
! S4 q, {' i& [: }8 z" i// iterations# @: G' G U7 b6 H5 ^$ ]4 f; C
int iter =1;; k& y! i7 y7 d1 r8 y( u. x
int iterations =5000; //迭代最大周期
- q( U$ m: \% {, m7 ^" X: ^$ I4 c0 u
// loop
& a5 H- t# i6 ?while (iter < iterations)& d' |8 u" j$ J B3 |; Q' S$ x
{/ C- R, g; i) \$ U% _2 F. I; ?; F
// run one epoch of genetic algorithm
- ]% I) [# r0 g: Upopulation.RunEpoch();
3 }, c$ N1 `7 {) F
/ g. H4 R% m ~// increase current iteration! @# s7 E" p/ G1 b6 _
iter++;) V9 [, Z3 _8 i6 J, t
}
) c5 ?0 `: w7 Z4 K0 P( s" r& ^- w- u
System.Console.WriteLine("遍历路径是: {0}", ((PermutationChromosome)population.BestChromosome).ToString());
4 ?; E; a0 w7 L OSystem.Console.WriteLine("总路程是:{0}", fitnessFunction.PathLength(population.BestChromosome));& ^' x2 T4 b m* \$ e4 W1 i6 `0 v7 |
System.Console.Read();
7 G% h8 T3 P# S: L
( o: J! M7 @3 F. U' }9 R( E}
0 ~0 ~- q' e; L- u}
% s' Y9 G: K) z}& i7 j6 R9 `) E2 ?/ x" ?6 q8 n
r( t- `! U; c2 X/ L0 c
5 A1 v, E. }* d3 y
[url=]
[/url]$ L9 H* ~& n$ t2 ?' A( j9 O
9 }2 O' Y( ?: s) H, h- ]8 G7 D3 t
/ c, n! @! O+ E' b R
3 N' @3 b+ K6 e% }/ e. O2 o& l4 e" D& \
2 o+ G% M6 u. A8 ?0 f/ A+ O网上据称这组TSP数据的最好的结果是 15404 ,上面的程序我刚才试了几次最好一次算出了15402.341,但是最差的时候也跑出了大于16000的结果。
我这还有一个版本,设置种群规模为1000,迭代5000次可以算出15408.508这个结果。源代码在文章最后可以下载。
& W% F8 J/ S" F/ V; O0 I总结一下使用AForge.Genetic解决问题的一般步骤:
(1) 定义适应函数类,需要实现IFitnessFunction接口
(2) 选定种群规模、使用的选择算法、染色体种类等参数,创建种群population
(3)设定迭代的最大次数,使用RunEpoch开始计算
F5 g! H" S( f! @4 P0 ~& B' N