# s$ ^. B" A# D0 v1 ]
优化算法入门系列文章目录(更新中):
遗传算法 ( GA , Genetic Algorithm ) ,也称进化算法 。 遗传算法是受达尔文的进化论的启发,借鉴生物进化过程而提出的一种启发式搜索算法。因此在介绍遗传算法前有必要简单的介绍生物进化知识。
' v$ T% I; y" Y. }7 L ]3 ^
1 j8 a: R( a) ~# o一.进化论知识 作为遗传算法生物背景的介绍,下面内容了解即可:
种群(Population):生物的进化以群体的形式进行,这样的一个群体称为种群。
个体:组成种群的单个生物。
基因 ( Gene ) :一个遗传因子。
染色体 ( Chromosome ) :包含一组的基因。
生存竞争,适者生存:对环境适应度高的、牛B的个体参与繁殖的机会比较多,后代就会越来越多。适应度低的个体参与繁殖的机会比较少,后代就会越来越少。
遗传与变异:新个体会遗传父母双方各一部分的基因,同时有一定的概率发生基因变异。
3 w6 S2 F/ ?, b5 }* }
简单说来就是:繁殖过程,会发生基因交叉( Crossover ) ,基因突变 ( Mutation ) ,适应度( Fitness )低的个体会被逐步淘汰,而适应度高的个体会越来越多。那么经过N代的自然选择后,保存下来的个体都是适应度很高的,其中很可能包含史上产生的适应度最高的那个个体。
F/ g5 l3 F6 T
9 F3 J0 S# u0 g$ @" Z
二.遗传算法思想 借鉴生物进化论,遗传算法将要解决的问题模拟成一个生物进化的过程,通过复制、交叉、突变等操作产生下一代的解,并逐步淘汰掉适应度函数值低的解,增加适应度函数值高的解。这样进化N代后就很有可能会进化出适应度函数值很高的个体。
举个例子,使用遗传算法解决“0-1背包问题”的思路:0-1背包的解可以编码为一串0-1字符串(0:不取,1:取) ;首先,随机产生M个0-1字符串,然后评价这些0-1字符串作为0-1背包问题的解的优劣;然后,随机选择一些字符串通过交叉、突变等操作产生下一代的M个字符串,而且较优的解被选中的概率要比较高。这样经过G代的进化后就可能会产生出0-1背包问题的一个“近似最优解”。
# |( b- W" h. t2 t5 e# U# g; D" W
编码:需要将问题的解编码成字符串的形式才能使用遗传算法。最简单的一种编码方式是二进制编码,即将问题的解编码成二进制位数组的形式。例如,问题的解是整数,那么可以将其编码成二进制位数组的形式。将0-1字符串作为0-1背包问题的解就属于二进制编码。
% s1 C" {+ V; Y* }; {8 | 遗传算法有3个最基本的操作:选择,交叉,变异。
9 k2 L4 J- m3 N9 ?
选择:选择一些染色体来产生下一代。一种常用的选择策略是 “比例选择”,也就是个体被选中的概率与其适应度函数值成正比。假设群体的个体总数是M,那么那么一个体Xi被选中的概率为f(Xi)/( f(X1) + f(X2) + …….. + f(Xn) ) 。比例选择实现算法就是所谓的“轮盘赌算法”( Roulette Wheel Selection ) ,轮盘赌算法的一个简单的实现如下:
4 [. j& m8 M7 s7 L/ v6 s" \' V
[url=]
[/url]# q% _" G+ n8 x$ c v
轮盘赌算法/*
5 \% Y) W1 E- G* 按设定的概率,随机选中一个个体
/ n% G9 q9 k; ?2 T* l- ~' _* P表示第i个个体被选中的概率/ Q: k: v1 j& I* N! Q
*/& y/ V- H/ j$ G/ v2 A0 [
int RWS()
* N6 o+ M7 r* A/ g3 r$ h{
- u; m& X5 c, k0 g' t7 Bm =0;& D( e( X1 F0 n. U2 p5 r1 `
r =Random(0,1); //r为0至1的随机数
7 w# L2 e8 B2 B# v* D1 K, {for(i=1;i<=N; i++)
& i) k. R2 T% S5 {5 [$ Y" f) o{
2 I( j( V1 ?3 P/ l/* 产生的随机数在m~m+P间则认为选中了i
) n% x. x; ?1 y* 因此i被选中的概率是P/ B0 Z6 s& h7 M- U" j O
*/
9 Q) P) |- y' [3 em = m + P;% B0 Z& l: e5 H! F7 A; \2 F
if(r<=m) return i;
( [7 r; B- y$ r}! D. A `+ P' R; C
}
0 H* V' W2 v( R9 ~7 V% d' A5 _' t3 d4 P9 }" o4 M9 V
[url=]
[/url]3 R0 i7 _) G9 m& J* S
0 H! X, x) q' { O% | C- u, E
' P' g( v. x) O, r3 U' V1 i
交叉(Crossover):2条染色体交换部分基因,来构造下一代的2条新的染色体。例如:
交叉前:
00000|011100000000|10000
11100|000001111110|00101
交叉后:
00000|000001111110|10000
11100|011100000000|00101
染色体交叉是以一定的概率发生的,这个概率记为Pc 。
, ]* w( K6 K% K @. ^. T) S( c
变异(Mutation):在繁殖过程,新产生的染色体中的基因会以一定的概率出错,称为变异。变异发生的概率记为Pm 。例如:
变异前:
000001110000000010000
变异后:
000001110000100010000
适应度函数 ( Fitness Function ):用于评价某个染色体的适应度,用f(x)表示。有时需要区分染色体的适应度函数与问题的目标函数。例如:0-1背包问题的目标函数是所取得物品价值,但将物品价值作为染色体的适应度函数可能并不一定适合。适应度函数与目标函数是正相关的,可对目标函数作一些变形来得到适应度函数。
* ~$ W" T! z# c7 b4 ~) ~
) J! u! Y+ i' B* d0 G- ^% w三.基本遗传算法的伪代码
6 e. R, |( W3 h' I
) ?2 r- t6 [& O# M[url=]
[/url]
9 l+ s7 W, x! s: f% G
基本遗传算法伪代码/*
( F( U! P0 v* K* Pc:交叉发生的概率! [3 D7 K7 k" Z. q- A9 c1 Y, x4 ^( J$ e
* Pm:变异发生的概率* {0 M, V/ w( E2 l, i! h
* M:种群规模
; P: |- a. X/ B! ~$ ^! O* G:终止进化的代数
! x& {3 B. m! l. @) o$ y: i* Tf:进化产生的任何一个个体的适应度函数超过Tf,则可以终止进化过程
& s1 z; r! W% X! W9 }3 v*/1 r6 i4 i6 {2 Q. t; D
初始化Pm,Pc,M,G,Tf等参数。随机产生第一代种群Pop" {. ^% e$ ?( K- `
- {5 [& |3 F& r8 G: y
do
7 c7 o h2 d, `$ h8 d{ ' K' p" y8 t& O
计算种群Pop中每一个体的适应度F(i)。- T2 s, G& D; G9 B, ]
初始化空种群newPop+ ~9 X t7 i1 ]$ W! b6 W
do
' S w0 {3 s1 G4 }3 l: {% J {- h2 T* W; S1 Y
根据适应度以比例选择算法从种群Pop中选出2个个体 `- R' `: G4 X2 `
if ( random ( 0 , 1 ) < Pc )2 k! ]: A6 `( s
{
+ P/ D$ t! @) d# D/ C 对2个个体按交叉概率Pc执行交叉操作
: ~+ k- y* b/ |% J }
/ |8 s( G5 ~- Q0 v if ( random ( 0 , 1 ) < Pm )! e* T7 ~) `, X6 m( s
{5 `( S; O, Y, K( ]: D5 Y& P
对2个个体按变异概率Pm执行变异操作, P8 X- h- F7 {9 r8 O! ?, U
}; @5 t$ Q d) A8 e0 N
将2个新个体加入种群newPop中
* W; ]% q3 s4 `} until ( M个子代被创建 )
* B5 ~ d8 J; |用newPop取代Pop: z( r% k$ N6 l0 {; d! |2 d1 h# ~
}until ( 任何染色体得分超过Tf, 或繁殖代数超过G )
, ^5 |! p" [) U I* d$ ^
" W* K2 C" j$ o0 }# b' _4 v/ t, E* g
* B, W C2 p( T O) v[url=]
[/url]
1 m+ F; m. O9 ~8 T5 D8 P/ p/ F0 t, x4 ?: O% f& R6 u
& B+ z! G* V- k( p, J Q2 C i
) z' o$ z& P7 _4 m( T, d- _3 o3 @7 ^四.基本遗传算法优化 下面的方法可优化遗传算法的性能。
精英主义(Elitist Strategy)选择:是基本遗传算法的一种优化。为了防止进化过程中产生的最优解被交叉和变异所破坏,可以将每一代中的最优解原封不动的复制到下一代中。
插入操作:可在3个基本操作的基础上增加一个插入操作。插入操作将染色体中的某个随机的片段移位到另一个随机的位置。
五. 使用AForge.Genetic解决TSP问题 AForge.NET是一个C#实现的面向人工智能、计算机视觉等领域的开源架构。AForge.NET中包含有一个遗传算法的类库。
/ ]0 {! a1 ?3 r* S2 D) b0 e' Q' ]. `; o$ @
介绍一下AForge的遗传算法用法吧。AForge.Genetic的类结构如下:
* c8 ?: J$ d3 m8 V

图1. AForge.Genetic的类图
0 ]/ `. P _7 C4 L h# W, M. s0 f' Q/ G- {; E* x- s, v: p) a
下面用AForge.Genetic写个解决TSP问题的最简单实例。测试数据集采用网上流传的中国31个省会城市的坐标:
% o) _- i/ l! U. C. t: B8 U
[url=]
[/url]) j4 o3 [% w. _
13042312
3 ]5 Y) W3 l4 B: I- ` H& L36391315
7 q/ j$ E6 W, J) B7 s7 p8 y" q, h41772244
! g* w0 V) o3 K37121399
( J1 p7 d1 Z$ w; ^7 a4 J34881535
) A' }: Y O% V' C. ^$ W% L33261556) r1 ?7 p9 a G# M+ M+ @
32381229
" w! c3 c5 L3 h f4 O41961004
! u5 }4 L* f5 b. E! O4312790$ }$ Q8 w$ B, ?% r; q y
4386570 G1 e" a& m% U( B, B
30071970# i6 x) n" [8 w+ {3 I, F0 U8 v5 t
25621756" E! _, ~) J5 m8 O' Z( ^; V
27881491
; d$ ~1 q4 _. f, X, R23811676
! W8 }! _7 @8 L1 ~2 Z6 P: X1332695
3 M$ U" O7 m+ t) s37151678
; T- K7 C5 Z0 `" a! y391821796 g! M5 p, j; ]5 A3 y
40612370! X) \ T) w7 }+ t' x
37802212
) H4 ?( X/ A% ~5 N36762578
+ r9 G3 \+ I! _; f1 F3 A8 S40292838, j% N! t* \7 F O9 r
42632931
! ~ w& y: W# x34291908
: x* l# K: x- }3 S2 H0 M35072367+ ]2 _$ g# h% \6 x7 U: Y% o# A9 c
33942643. {/ e* X' o7 h) m A
34393201
1 N: N4 _" ^* J& ]$ D29353240% m0 j1 S8 a: t: O4 Q
31403550
6 P5 O' f H2 B1 z4 ? D5 v254523574 I( X& T& @! S; p
27782826/ y$ B: q. g- h4 T
23702975' k' G3 V" X8 I+ W7 x2 T) q8 n
[url=]
[/url]
_* z! f3 j3 j" ]! ~
M: Z& E7 i$ j0 R" ] _/ m- L" G" ?2 Z3 C3 F1 H) X0 S
( p) m6 N; n3 t! y& P
7 g! q* C+ ?. s% D j9 o: j操作过程:
(2) 创建C#空项目GenticTSP。然后在AForge目录下找到AForge.dll和AForge.Genetic.dll,将其拷贝到TestTSP项目的bin/Debug目录下。再通过“Add Reference...”将这两个DLL添加到工程。
(3) 将31个城市坐标数据保存为bin/Debug/Data.txt 。
(4) 添加TSPFitnessFunction.cs,加入如下代码:
, |; e0 @+ r0 ^7 S' K) R }
[url=]
[/url]
9 G) N# z$ ^& I& H
TSPFitnessFunction类using System;2 H. D! G+ D5 x4 Q; V% K4 q5 x1 v
using AForge.Genetic;
. t8 `! O% K+ x+ U; i0 D8 ?* w& _5 r7 }7 ~
namespace GenticTSP& l4 f" b0 W# j( U) O$ Z) ~: G
{
+ p- ?+ K7 a, S/ ?/ K2 z( u///<summary># H2 ]! G7 Z8 D7 z0 w; m
/// Fitness function for TSP task (Travaling Salasman Problem)6 w9 p3 C" u8 \5 X
///</summary>
s2 I6 m2 m8 b7 qpublicclass TSPFitnessFunction : IFitnessFunction
9 f. n p4 y9 L! Z" ?{$ m1 V8 |- M# v& v6 b+ F1 C
// map) f; }9 o& A( S4 m: A
privateint[,] map =null;# M$ f5 _ P1 ~0 j- @9 Z/ [
( \ ^ S- _% E+ M; M* s5 {4 X// Constructor f8 w: u' D9 y$ N4 p: a
public TSPFitnessFunction(int[,] map): b; ?7 V' l6 b: b: _2 r% B
{/ Z2 D l3 Z# a* X, X- H* h* {
this.map = map;
# D% u6 F0 Y' _}
$ r' J/ u7 X) ]) ?8 q# l3 {: b' T% b. |1 V* s8 }8 ?+ s0 `) B Q
///<summary>
% S9 R [9 J: r! `+ l7 T/// Evaluate chromosome - calculates its fitness value- m$ j/ L/ K. R: w6 ]- l. u- h# \3 c
///</summary>( {7 o# d1 N# s& T+ e- h- `
publicdouble Evaluate(IChromosome chromosome); ~+ C; _1 X3 i
{
: X7 _ R. m* S0 [return1/ (PathLength(chromosome) +1);
# I8 v- W8 r6 }5 G. [% P6 c}; h5 \! O5 N- A1 A
: N# D, S$ T3 \$ q! ]
///<summary>. p! E2 B# @' S# Z( u1 t
/// Translate genotype to phenotype * B/ w' w/ P" M9 v" Q
///</summary>
- ?. p1 L8 I1 X5 K, M! Kpublicobject Translate(IChromosome chromosome)# Z5 P5 |8 O7 i
{$ ~1 L. A# T I
return chromosome.ToString();9 Z8 p+ B) u$ V2 e2 t/ q1 N
}1 h# y' d& i3 D5 y# \, t6 ]
, u3 t7 m/ b$ g& c3 l: Q6 r///<summary>7 {$ z1 |" u8 ]
/// Calculate path length represented by the specified chromosome
1 @$ ~/ a3 b. v# d+ B8 r2 ]1 Z///</summary>
! D) A# O: Y4 B* v! ipublicdouble PathLength(IChromosome chromosome)6 j8 A( n+ a9 s/ X6 a- N! I9 ]6 u
{: c/ K# W4 j9 K# t/ g5 ?4 _! V" [
// salesman path
! r9 X& [( r* G6 n- N: Jushort[] path = ((PermutationChromosome)chromosome).Value;
7 h$ c7 I0 y) d. z1 V. {& G
8 N a+ `2 D2 f5 k/ ]+ s// check path size$ Q( H# V& T; C6 i
if (path.Length != map.GetLength(0))
- @+ k( t3 l3 ^6 y. \( s, N5 e0 Z{1 i+ T; l: P. ^* f
thrownew ArgumentException("Invalid path specified - not all cities are visited");
7 N0 V( `4 g# y5 G' V}+ t; [0 `2 a* j5 w0 P7 M/ F6 y
& a7 Z, U& _3 b. g* u* l9 t2 @! b6 M' f// path length
( J# Z% M5 M/ Pint prev = path[0];
6 f" F, t5 C& N' a+ u+ l* S! L3 R4 Kint curr = path[path.Length -1];
: E, Z' \ B8 }6 G. ]8 g0 \& t4 j2 q
* H* z5 {+ G) p# {// calculate distance between the last and the first city
; L& P, K" m* b. rdouble dx = map[curr, 0] - map[prev, 0];) u) b4 N" J0 t% f( Q: k
double dy = map[curr, 1] - map[prev, 1];5 l7 o: S0 H% `8 r4 q" c" _
double pathLength = Math.Sqrt(dx * dx + dy * dy);2 S% ?3 r; d( H( y! ~) E
. Z1 t- c5 h( s( s; g# {$ q// calculate the path length from the first city to the last
+ k U8 Z1 B( S7 Pfor (int i =1, n = path.Length; i < n; i++)" G2 }- u( ~& P' z1 [9 R
{5 M: L: J0 j4 C7 y) w
// get current city
/ D' S: u3 z- e* k, [' Q; |curr = path;
" i1 n* s/ O" j6 M4 B( t, I6 ~0 Q" p9 p2 P6 I6 ], n0 \! w
// calculate distance
8 X; S2 Q! ?% }+ f0 }dx = map[curr, 0] - map[prev, 0];1 h% O; u. S! F9 p$ m
dy = map[curr, 1] - map[prev, 1];$ D+ d" m/ e, |) l$ q( E: k
pathLength += Math.Sqrt(dx * dx + dy * dy);
5 S' N0 G: j" L+ S% U" V' u( E0 q" R$ N! T0 M
// put current city as previous
+ G9 M8 T0 |7 Q4 z2 y$ Uprev = curr;
+ b1 E. \, ~; H- }5 @7 _}# Y. N4 ~' {& \/ G; ^' K" y
6 Z' m" V, X# Y5 C: B6 `
return pathLength;* T" \* ~1 f/ Q
}/ y! w7 o4 X! t c
}
& `3 A4 x" H }}
' T; R r6 l5 v/ A( G$ ~
2 {! P+ G1 b- b: E. i
8 N* y$ M7 b, d- `0 ]1 i; p[url=]
[/url]; p3 B+ D1 V5 p8 D
+ [0 N8 _! _7 B! y: O9 v/ a: Y
# y- r' \% }8 K
# s: j9 n/ l3 P; |& } (5) 添加GenticTSP.cs,加入如下代码:
7 E: j) j1 O V+ P6 [[url=]
[/url]9 n; Y2 R( F4 G& e7 C. v
GenticTSP类using System;! W7 o* L0 Q& ^8 J4 l3 d
using System.Collections.Generic;
/ F6 G& [4 { c; M/ x2 K2 b$ L- ]5 wusing System.Linq;( S! c& S& d! ^9 |9 z
using System.Text;
9 V2 ~0 g3 u" T9 c; K- l$ c$ qusing System.IO;
7 l' h2 L2 N3 D& Q- u# a1 n* [$ Q+ G: f/ w! y
using AForge;0 N6 [# `7 b: D( k% u. b; X2 {
using AForge.Genetic;
9 e) ?1 B$ j% G1 W( d- g1 B
" s. {( n$ A& N2 o( Q h' B v; m
; U/ H* {/ N0 R2 J2 Unamespace GenticTSP
! o+ M" V* h& ^0 k{
' K7 o L- v1 e+ w9 F1 M! ]class GenticTSP; A+ l4 {% h% a& U) T8 Y1 c
{# z( F) l* C! [$ \
! T, w- k! @% x/ d/ O' P; Y( e9 w
staticvoid Main()- `' n+ }/ E9 j
{
, ^+ r$ J3 E* P9 e2 E+ iStreamReader reader =new StreamReader("Data.txt");
- r' L$ X5 `! O% {2 |; s, t( o+ Q9 }& c5 I( O1 W9 U" r
int citiesCount =31; //城市数
8 W3 w, j8 E2 [: u a! m: H2 |9 d' r& I3 \
int[,] map =newint[citiesCount, 2];0 u" R$ E4 u& A( P5 i& r
7 e6 n/ ]. C8 }! s
for (int i =0; i < citiesCount; i++)5 p' y! T) C6 I
{
; ~& z* O) L- @) }string value = reader.ReadLine();9 Y9 e: R5 O* t1 `% D/ ]
string[] temp = value.Split('');3 c7 d+ S2 @) A
map[i, 0] =int.Parse(temp[0]); //读取城市坐标4 `. V. _: w$ k* k# t7 s
map[i, 1] =int.Parse(temp[1]);
. K' I* G3 Z I$ o; ]}
" J( _4 T3 B+ f* V; C" `$ p! Y* @; |
// create fitness function3 P1 K# M0 ~1 d8 _6 v# G* F" X
TSPFitnessFunction fitnessFunction =new TSPFitnessFunction(map);
6 O. o: V/ w- l' U N5 g* g
" o3 x2 s8 @& |/ |! \6 U/ Uint populationSize = 1000; //种群最大规模
6 l" d$ u h. \; a
# ^3 l# [2 \8 @+ q+ t/* 1 E0 f* e) F5 T0 K0 g. t; y
* 0:EliteSelection算法
9 d! D0 |5 r+ j m* 1:RankSelection算法 ; E4 d2 l& o) o$ m' y/ ^# ^
* 其他:RouletteWheelSelection 算法
0 N( M9 |/ N' R6 m! D' L1 B- o* */! S1 d2 o, p. I& t3 i
int selectionMethod =0;- b" z% L- h! {6 \
$ |# J# R# I* ?$ t3 j' Z
// create population+ b. u6 t# m A7 ^% ?5 A+ N
Population population =new Population(populationSize,
2 j' A" s. ~- Inew PermutationChromosome(citiesCount),
! G/ P4 b1 i3 bfitnessFunction,' W& v3 Z- K B
(selectionMethod ==0) ? (ISelectionMethod)new EliteSelection() :
! e7 Z2 c$ A- ?( u(selectionMethod ==1) ? (ISelectionMethod)new RankSelection() :
0 j o4 w) k( N(ISelectionMethod)new RouletteWheelSelection()' |6 }5 ?9 p( Q, N4 l% d. S9 E
);. `4 [/ i1 x. G- V9 {* Z
2 t) F9 [# y. ^, e- X
// iterations9 T1 @- s2 x8 u |
int iter =1;, b- H9 J2 v$ h. m( v- F+ ?
int iterations =5000; //迭代最大周期
! t8 K m; z% \
& |, g0 O7 W6 C8 ~! |// loop
# l7 s Y/ g; J% i1 a6 Awhile (iter < iterations): s* `; \1 N. i6 h$ F0 i1 L6 t
{, l$ q# w, r! [( C; _
// run one epoch of genetic algorithm; p* g4 p- s& g8 P' H$ o
population.RunEpoch();
8 `5 g% `4 Y T" r! z- @; m
8 ^% z1 V( q7 K6 t6 o// increase current iteration# \6 u6 g% \$ ~
iter++;
! b0 m# e9 T( J% \}
7 T7 b; r) h- `' ~2 u4 t/ S+ p0 j5 F5 ] R
System.Console.WriteLine("遍历路径是: {0}", ((PermutationChromosome)population.BestChromosome).ToString());% g c9 i3 V. h3 E: |
System.Console.WriteLine("总路程是:{0}", fitnessFunction.PathLength(population.BestChromosome));
. o9 N3 J5 c y! U% w: _System.Console.Read();9 }4 \* ] T$ B, N
; z& {% v5 V c3 Z; o; \' z" m; S}
6 @ y6 n+ {4 Y/ u* T# q2 ]}
3 E1 E/ C: u# |1 I; d& J) Q}
! u4 o4 Y; e2 R$ i3 D E& t# S% ~7 B7 A Y9 \
5 R! P: c& ]/ V% l
[url=]
[/url]; G8 M3 P- I+ o, E2 N+ R+ i2 O
: Z2 z4 P/ `7 t0 v* M; W- r4 ~5 u& a# h/ Y& C( {! _
) u8 m' u6 j% p7 s; l" U# P
% G5 g6 C% C! i! E7 U% J* W# i
网上据称这组TSP数据的最好的结果是 15404 ,上面的程序我刚才试了几次最好一次算出了15402.341,但是最差的时候也跑出了大于16000的结果。
我这还有一个版本,设置种群规模为1000,迭代5000次可以算出15408.508这个结果。源代码在文章最后可以下载。
; ^2 g# i' Y; K: s% G总结一下使用AForge.Genetic解决问题的一般步骤:
(1) 定义适应函数类,需要实现IFitnessFunction接口
(2) 选定种群规模、使用的选择算法、染色体种类等参数,创建种群population
(3)设定迭代的最大次数,使用RunEpoch开始计算
8 f$ J* E5 I# n" |