: } B! ?0 O2 K# o" d0 n" C, W
优化算法入门系列文章目录(更新中):
遗传算法 ( GA , Genetic Algorithm ) ,也称进化算法 。 遗传算法是受达尔文的进化论的启发,借鉴生物进化过程而提出的一种启发式搜索算法。因此在介绍遗传算法前有必要简单的介绍生物进化知识。
/ s2 Z# w, }* C* i+ b% d
; S0 G3 u4 ~, t1 Y: C一.进化论知识 作为遗传算法生物背景的介绍,下面内容了解即可:
种群(Population):生物的进化以群体的形式进行,这样的一个群体称为种群。
个体:组成种群的单个生物。
基因 ( Gene ) :一个遗传因子。
染色体 ( Chromosome ) :包含一组的基因。
生存竞争,适者生存:对环境适应度高的、牛B的个体参与繁殖的机会比较多,后代就会越来越多。适应度低的个体参与繁殖的机会比较少,后代就会越来越少。
遗传与变异:新个体会遗传父母双方各一部分的基因,同时有一定的概率发生基因变异。
3 ~7 j! e% D( H& p. y7 V2 H
简单说来就是:繁殖过程,会发生基因交叉( Crossover ) ,基因突变 ( Mutation ) ,适应度( Fitness )低的个体会被逐步淘汰,而适应度高的个体会越来越多。那么经过N代的自然选择后,保存下来的个体都是适应度很高的,其中很可能包含史上产生的适应度最高的那个个体。
4 x& d7 Y% d% H* ~6 B7 L, p% |
* x* G- y7 i* w3 t! I$ n' V3 ~二.遗传算法思想 借鉴生物进化论,遗传算法将要解决的问题模拟成一个生物进化的过程,通过复制、交叉、突变等操作产生下一代的解,并逐步淘汰掉适应度函数值低的解,增加适应度函数值高的解。这样进化N代后就很有可能会进化出适应度函数值很高的个体。
举个例子,使用遗传算法解决“0-1背包问题”的思路:0-1背包的解可以编码为一串0-1字符串(0:不取,1:取) ;首先,随机产生M个0-1字符串,然后评价这些0-1字符串作为0-1背包问题的解的优劣;然后,随机选择一些字符串通过交叉、突变等操作产生下一代的M个字符串,而且较优的解被选中的概率要比较高。这样经过G代的进化后就可能会产生出0-1背包问题的一个“近似最优解”。
- [" O; k; H: h" |6 T/ e 编码:需要将问题的解编码成字符串的形式才能使用遗传算法。最简单的一种编码方式是二进制编码,即将问题的解编码成二进制位数组的形式。例如,问题的解是整数,那么可以将其编码成二进制位数组的形式。将0-1字符串作为0-1背包问题的解就属于二进制编码。
% \# \0 K, ~# z' H" y8 N
遗传算法有3个最基本的操作:选择,交叉,变异。
! _+ J. }0 M- Q& N+ l' {
选择:选择一些染色体来产生下一代。一种常用的选择策略是 “比例选择”,也就是个体被选中的概率与其适应度函数值成正比。假设群体的个体总数是M,那么那么一个体Xi被选中的概率为f(Xi)/( f(X1) + f(X2) + …….. + f(Xn) ) 。比例选择实现算法就是所谓的“轮盘赌算法”( Roulette Wheel Selection ) ,轮盘赌算法的一个简单的实现如下:
0 L3 Y ~# }. r. H1 ~
[url=]
[/url]7 D3 [8 i7 Y) a) T N/ X' y8 g
轮盘赌算法/*
/ o! P. L$ F) h( X! ~* 按设定的概率,随机选中一个个体
* c0 H& f1 n- M7 Y) D' D* P表示第i个个体被选中的概率9 j }% L# F3 A
*/, J* }: {' ?# Z/ H. \2 l
int RWS()# l2 j; k1 i6 N: a3 [- v- P& Y: d
{0 p! [2 q) v( B6 n- L$ ?
m =0;
1 x/ w: l/ k9 hr =Random(0,1); //r为0至1的随机数0 Y+ k1 e, H6 n" T, O3 l* w
for(i=1;i<=N; i++)
u" d6 O6 Z7 d; h l4 ]+ z/ M{, s' q1 X. B% m4 \/ k3 i
/* 产生的随机数在m~m+P间则认为选中了i ?/ x5 l7 G. E3 t8 y
* 因此i被选中的概率是P/ C& W# }8 ]" a4 _5 |2 B5 v
*/6 ]& r7 J# i1 U
m = m + P;
, Q- W2 ?0 V' Aif(r<=m) return i;
9 ]% P' R2 ~" c; \}+ y2 e1 k. q$ R! j+ n) @
}
$ J8 t- J) \5 }9 V9 Z
6 R- \- @8 {0 Q8 x. @$ i[url=]
[/url]; y* z5 A( H9 f X
* J. r" o4 y9 R8 E3 T* Z+ ?$ L
6 u1 Q. H- @4 Y! u; B% w$ h* u
交叉(Crossover):2条染色体交换部分基因,来构造下一代的2条新的染色体。例如:
交叉前:
00000|011100000000|10000
11100|000001111110|00101
交叉后:
00000|000001111110|10000
11100|011100000000|00101
染色体交叉是以一定的概率发生的,这个概率记为Pc 。
% z! N; o$ t# Q变异(Mutation):在繁殖过程,新产生的染色体中的基因会以一定的概率出错,称为变异。变异发生的概率记为Pm 。例如:
变异前:
000001110000000010000
变异后:
000001110000100010000
适应度函数 ( Fitness Function ):用于评价某个染色体的适应度,用f(x)表示。有时需要区分染色体的适应度函数与问题的目标函数。例如:0-1背包问题的目标函数是所取得物品价值,但将物品价值作为染色体的适应度函数可能并不一定适合。适应度函数与目标函数是正相关的,可对目标函数作一些变形来得到适应度函数。
! b4 w- L9 k! R5 z! v, H4 |) J4 Z
$ H5 G! l3 m/ X2 B" N e三.基本遗传算法的伪代码3 A' M6 J) ?0 Q9 r, E$ E
& k. z' ^% P- m- ]- F
[url=]
[/url]
0 k& X0 p' q+ Q" q+ Q
基本遗传算法伪代码/*
: D9 I1 b( z5 C* Pc:交叉发生的概率
3 I& f r- v9 B* Pm:变异发生的概率
4 y* ~" \2 a4 c# ?" `. n* M:种群规模$ y+ b4 d: A, f) Z* U/ x9 O1 V
* G:终止进化的代数
8 Z! X0 h' O8 k" t* Tf:进化产生的任何一个个体的适应度函数超过Tf,则可以终止进化过程
: a+ X& A+ }! f/ N4 `; L- d*/
& c, D4 p; ?( |) Z% a% _7 ?/ f初始化Pm,Pc,M,G,Tf等参数。随机产生第一代种群Pop
6 z$ t$ Q5 f" G' \3 }. e
9 A1 Y& t6 `" ^7 W |do
" e& L% H" E) P9 A3 J7 M{ ) R. M0 c8 d* G/ H/ }. V+ u2 i
计算种群Pop中每一个体的适应度F(i)。
; z+ M: ?0 g# y% Z 初始化空种群newPop
" b* [( T S' V do. @5 O& j+ ~% o1 c$ Z$ O) i7 c' v
{. ?+ ~( h0 \5 f. ~1 a |$ i
根据适应度以比例选择算法从种群Pop中选出2个个体& {2 M5 l. L( ^$ w& A
if ( random ( 0 , 1 ) < Pc )
; q$ G( a# }3 Y0 `: [ {
: C1 c9 I2 ~& R" ]# c7 L 对2个个体按交叉概率Pc执行交叉操作& d% Z/ Y: K$ Z- c8 f Z7 w& [
}
6 j% R( v- R8 _* v+ C8 b if ( random ( 0 , 1 ) < Pm )
0 e3 ]% `1 K Z9 W! P n4 ^ {
& o, Q+ f4 _+ x- D+ h1 V 对2个个体按变异概率Pm执行变异操作7 T! _ K3 {3 k
}, N4 K+ j9 O- {; q0 p
将2个新个体加入种群newPop中0 H9 p0 C* D9 L8 J
} until ( M个子代被创建 )8 ?: D- _! I% b D( O7 O0 r
用newPop取代Pop
) o+ E) o) s# a- ^}until ( 任何染色体得分超过Tf, 或繁殖代数超过G )* Z' P4 P7 h0 w9 f; y
* [; D1 Y) e& u7 g$ y
+ r- d/ h3 `! ][url=]
[/url]
! x) X1 W9 U; W7 L' T% z E) e2 j/ F/ x5 ^
( b* u9 e* A& E; E4 z r7 @4 Z X0 M1 |. [
4 }2 N+ j7 m3 A K3 A四.基本遗传算法优化 下面的方法可优化遗传算法的性能。
精英主义(Elitist Strategy)选择:是基本遗传算法的一种优化。为了防止进化过程中产生的最优解被交叉和变异所破坏,可以将每一代中的最优解原封不动的复制到下一代中。
插入操作:可在3个基本操作的基础上增加一个插入操作。插入操作将染色体中的某个随机的片段移位到另一个随机的位置。
五. 使用AForge.Genetic解决TSP问题 AForge.NET是一个C#实现的面向人工智能、计算机视觉等领域的开源架构。AForge.NET中包含有一个遗传算法的类库。
7 X3 p; m3 P1 i; S5 z
& _! G2 r# v4 d 介绍一下AForge的遗传算法用法吧。AForge.Genetic的类结构如下:
. W) N6 {& T# ]" i
图1. AForge.Genetic的类图
- n' O% b$ {* o4 a5 M. B
. L1 u) f) o% s' D 下面用AForge.Genetic写个解决TSP问题的最简单实例。测试数据集采用网上流传的中国31个省会城市的坐标:
9 q1 z& H* b# ?- `- W v1 k
[url=]
[/url]
; D( A# Z9 q' ?* ?& p3 b/ n+ r13042312
( T; n# a9 O4 ]; _7 S% I, d* f363913156 M" F6 n# v3 R' Z9 [
41772244/ y7 i$ i( q' }: T
37121399
- U) n7 l+ _ b- Q% ^34881535" z3 \ ~* H: c0 Z2 A6 O
33261556
1 b$ Y5 g, l2 e+ H7 S8 d323812299 y7 X& T A$ g1 K3 [/ O
41961004+ k, j6 I( s- V: P" A" \# r; E
4312790
6 l! }: ?: F2 g% ?, B$ d4386570
# Y3 ]. U! Y; u. s1 K) F& Y30071970
' s/ f& S" d7 V8 y7 J% x7 Z25621756
& i. F" _3 e5 ^27881491
. C- v( ^: Z R( r9 ~7 p23811676+ c9 ]* R" m, L+ B& F# Y
13326950 m1 i% j$ H! M* c8 \9 w' {
37151678
4 r: _. V4 h% l) ^6 |: _& }39182179( o: k3 v: I4 @: Q) H
40612370 K, b( V9 O8 o7 \
37802212 n. `. Y1 o( }9 s1 ~
36762578/ S, |) C3 j9 O
402928386 Y) F2 h, F! }4 Z7 o w" Z/ T
42632931
$ n: ~( o2 L5 S$ n34291908$ T" h$ w: D& j
35072367
# z4 M+ D/ ]& u33942643
9 d L Z& w% q+ ?. Q34393201 l6 b" T% K8 T, |
293532407 y$ }) w- K; ]" m
31403550
: Q6 `, b' A: m o% d25452357
2 _4 G: v# S! q3 Y27782826: B- q) c, c& F0 b0 I4 |5 O; O
23702975* K: i* l8 X& {2 ?7 j- f
[url=]
[/url]
, C8 }2 t% D' i! H/ b9 \" j/ I- |5 w4 z/ o8 f: O6 Q
4 c( }3 g* M. g, a2 L
# U1 _1 D! k5 ?* K4 _2 S4 l$ Q& I# N% e4 s
操作过程:
(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 I5 B& B- h/ `[url=]
[/url]
3 ?2 c1 m3 }" ~* A2 M
TSPFitnessFunction类using System;
3 `/ I A C* m1 b! vusing AForge.Genetic;
) w J X1 ~) y. m1 ~$ c$ }6 G$ V) g2 M
namespace GenticTSP; p4 O# O% r2 J" g6 O" [4 s; _& _
{' b/ u; H5 \( v; \: b5 r5 Z
///<summary>6 M3 H4 [& d% Q& G2 I" Y: v$ u
/// Fitness function for TSP task (Travaling Salasman Problem)$ x S. ?, O- E& n6 ?- {3 [
///</summary>
3 c! a0 t9 R; h# {, }0 n& [5 gpublicclass TSPFitnessFunction : IFitnessFunction
# d1 e! N, p& e% W3 s Q{
5 l( E& X' O: o) K. e& J" g// map
) A2 B9 x0 ?4 ]6 W' {$ Fprivateint[,] map =null;
4 y4 c( ~9 K2 }& n4 h* Y( ^+ i8 A( c1 \! }, n
// Constructor
4 d1 y, ]) X3 e2 X; q+ ?. K- Epublic TSPFitnessFunction(int[,] map)
6 I, y) _6 V; p3 M% b: J7 c{
$ a% k( x5 |: v& a' Jthis.map = map;5 A4 O! w. P# T: s% p% T+ [
}
( z0 f" w0 A! F8 ]
! c& C2 `+ r# N///<summary>2 p/ m8 j2 V: x$ G
/// Evaluate chromosome - calculates its fitness value: L/ f4 F4 f3 c) f/ c
///</summary>
7 t" {, J6 S) d3 e8 O3 V" bpublicdouble Evaluate(IChromosome chromosome)3 G6 F* V1 ]9 ~& q+ `
{
8 {# m8 M6 `* ] k" h2 I9 kreturn1/ (PathLength(chromosome) +1);
1 V* x/ |* W* l$ u- h% z1 |* ^}
( X8 j5 m& [7 b) G7 @+ l' h7 E" `. a9 I- v: B* ~
///<summary>
+ m- N5 x& \1 k2 ^/// Translate genotype to phenotype . ] C+ Q0 w# z) B5 |# r: O
///</summary>% P* v8 B% r* b2 M, U
publicobject Translate(IChromosome chromosome)
% y( N, ~% `9 A% H{5 |: L1 ?3 Z- P5 O2 k& Z
return chromosome.ToString();6 v. k: }& u) `
}
* L$ ?) G3 k# R! i
& e' b# l% w; d+ I5 B4 y; n5 ~3 X///<summary>5 w5 l, v/ t7 ~7 R
/// Calculate path length represented by the specified chromosome $ L# q+ J: Q$ o/ e! C. ^; k3 H
///</summary>
8 B1 Z* G* {3 t& a3 Jpublicdouble PathLength(IChromosome chromosome)% y9 \% }) Q* @5 `7 i4 H9 e
{
" d$ B: ~8 M' n5 y W- i1 N// salesman path; U0 t' q/ Z9 C
ushort[] path = ((PermutationChromosome)chromosome).Value;! q$ U; {7 z/ G$ v- \2 N+ h% Y
5 Y9 j6 p7 S$ \4 ^! g2 S
// check path size* h9 }; \8 h7 Y5 O! l7 R$ Z/ R
if (path.Length != map.GetLength(0))" l: t/ H b9 O3 c
{
' S3 z3 A' o0 i7 }thrownew ArgumentException("Invalid path specified - not all cities are visited");' ~1 z1 A0 G' J
}
: \1 N. p+ g# G) q0 C3 u
9 a a4 }1 A( V- @) O0 A& ~// path length
3 S3 t; H. j$ n4 y7 qint prev = path[0];# r+ p2 E6 m; I; \. s4 x* @
int curr = path[path.Length -1];
$ X8 |) h5 h7 R; P8 h3 T
! e+ D* n3 k: L# z& e4 h+ l& [// calculate distance between the last and the first city4 E$ Q) {+ U. p( t4 h
double dx = map[curr, 0] - map[prev, 0];
% w) Z: A2 }+ j: I- bdouble dy = map[curr, 1] - map[prev, 1];( _$ Z0 I3 T3 U3 u
double pathLength = Math.Sqrt(dx * dx + dy * dy);) J: F/ l4 {+ E Q) m
- o2 t. C/ ?. Q/ H" P// calculate the path length from the first city to the last
/ B( o% Q" ]4 [ D8 \for (int i =1, n = path.Length; i < n; i++)8 G, I3 [3 I: e
{- h' o5 u4 f3 g$ ^* ?" \
// get current city
8 X- a, P" T* s8 `( m# Icurr = path;
7 v* ?/ Z- R) j: o6 \3 u' c9 B# g
* l4 c6 d4 N4 c" ^// calculate distance
+ b: a: b% P" N* Pdx = map[curr, 0] - map[prev, 0];0 [( h% J% C5 L
dy = map[curr, 1] - map[prev, 1];
6 z) X* d& G* F8 e! z, R$ g8 x- ZpathLength += Math.Sqrt(dx * dx + dy * dy);% V) U. o9 D' M5 ~- P: \' x
0 y1 @1 g2 k) a) d( }7 _. f// put current city as previous1 M6 V6 L: z3 f& k3 }
prev = curr;
t) E5 t* t' ~4 H}# W: J! S; c: z( M, n& V( v
' Y3 T! |7 [7 f1 `8 [5 R# o. p. nreturn pathLength;: z: r H+ z- M4 e$ z( O
}" j" U" N; [( ~& Q5 ~
}9 t' T! X V. N
}
' w( R b" f7 e
" ` Y" t/ ] t4 y7 X9 V6 f) W) D; J4 L R1 v1 k
[url=]
[/url]7 L& |/ y8 b" I7 Q
1 l4 \5 j1 ~; e" F9 o
/ R, S- f. y6 U6 E2 V+ w, S+ z5 J1 s+ Q) K) O. s- u5 _8 u( K
(5) 添加GenticTSP.cs,加入如下代码:
2 [ _% T9 ^% u7 |" Q7 @, K* m
[url=]
[/url]
+ D6 k8 M) I: f* b
GenticTSP类using System;
* h. N. E# F( m8 K+ M5 l% gusing System.Collections.Generic;
* g8 m! J9 e, l/ M, d: W5 ~3 ousing System.Linq;2 ~* ]- y0 F, U- L9 T
using System.Text;
; S+ L) [7 S+ N, ^using System.IO;
3 y# e( l- N$ m7 u; V+ y; R/ C) k9 x2 u9 \& _8 e( ]5 R6 I
using AForge; w% l8 R) m: I' j
using AForge.Genetic;0 Z5 Z1 o# V# K8 X
3 r# n2 l0 H) @, W
6 |: {( r: u% Unamespace GenticTSP/ n9 v' { E0 q
{# P; }4 _/ g$ O T
class GenticTSP
- U4 p' ~ S8 E; {, V: l1 N{
9 `/ m2 S5 J: V/ X
5 g: o! {2 R' o$ Ustaticvoid Main()
# {# V; G! g1 l: B{" }$ m" ]( m6 `0 F, T
StreamReader reader =new StreamReader("Data.txt");; o- A" E% @3 R! ~# Y/ i# l- w
* A. a# q$ U* tint citiesCount =31; //城市数" \* z5 k0 B/ P3 ^
, G. ~# C- z1 [int[,] map =newint[citiesCount, 2];
0 }: Z7 J. B, |6 r3 F$ y: X6 i. C% C( y7 M g6 I4 G8 [
for (int i =0; i < citiesCount; i++)
l* ]' ?3 g: ~8 Y7 J1 E{0 v. s6 G2 J" W, l! s
string value = reader.ReadLine();) y( E2 Y0 C# H4 y
string[] temp = value.Split('');% w6 `9 x& E- q5 n7 V4 X0 Y
map[i, 0] =int.Parse(temp[0]); //读取城市坐标, E. O2 z6 i( _6 u
map[i, 1] =int.Parse(temp[1]);
/ P; c. D# I( }( I7 n; l# G}
4 `: H: J2 [* A- n8 C
, k: {, l" v* |$ e* y! M// create fitness function
- J/ C5 k( [3 JTSPFitnessFunction fitnessFunction =new TSPFitnessFunction(map);# X y t+ w. h3 k7 T U; o
: l. Z6 b, s8 X, A* h+ Qint populationSize = 1000; //种群最大规模6 R1 U$ I4 L' p/ q9 i0 w: K
9 u# a/ j7 u( B2 M$ F$ D- n/*
3 W; I9 A3 N8 b* 0:EliteSelection算法
! a& k5 B) Q6 z6 W* 1:RankSelection算法
) h& L/ Q! ~0 u; I4 Y( @$ b* 其他:RouletteWheelSelection 算法, Y. \' t+ k" t" ^1 J
* */
9 O# O1 R5 |: f7 h8 N( aint selectionMethod =0;3 |# d( K' Q+ |
7 L* D$ U/ e- [3 R5 u6 o! J
// create population( a8 N: p/ v# W$ b9 d2 p
Population population =new Population(populationSize,
5 R- I- z, ~0 w" j# pnew PermutationChromosome(citiesCount),
! N6 M, N6 ]! |fitnessFunction,
0 n2 c' ?3 ]9 P5 V6 @. R r(selectionMethod ==0) ? (ISelectionMethod)new EliteSelection() :
7 P% G% M5 U2 P4 V4 ~8 D(selectionMethod ==1) ? (ISelectionMethod)new RankSelection() :8 F7 p) Y8 |6 L; I9 @ T' _
(ISelectionMethod)new RouletteWheelSelection()9 @! \, m4 e- w
);
% O2 \7 g1 z$ H/ D
" k+ i' ~+ i' O. N3 \) F2 m. V// iterations: L( [# W% u8 o5 H
int iter =1;
, i5 K+ F+ J7 Q8 `" b) Hint iterations =5000; //迭代最大周期& |# s8 \9 v8 E. k: y6 g V# E d
& _1 H e1 V, I) T2 B// loop# P% w7 y( k& g5 x! ?5 m( g0 ?
while (iter < iterations). M* h. B/ e3 O+ Y- R) `5 c
{( ^4 T, X7 N7 {* w; M. _
// run one epoch of genetic algorithm3 n1 z2 u" s( V( u5 J
population.RunEpoch(); v9 ?: e' O7 X1 r8 }
+ Y3 ~8 `. [0 e" n! c* [// increase current iteration
/ Y: b7 O1 t7 e1 A$ l. ~, \iter++;- s, x- G- \7 T0 C; \9 o
}
' w8 _+ \4 m9 X1 F6 s6 p3 b; D" |# ~' C( B& O* V' u: ?0 y: w4 D% ^
System.Console.WriteLine("遍历路径是: {0}", ((PermutationChromosome)population.BestChromosome).ToString());8 {7 ?/ i4 f0 z! \
System.Console.WriteLine("总路程是:{0}", fitnessFunction.PathLength(population.BestChromosome));
( T; r X7 U; u4 C2 ^' rSystem.Console.Read();
`% f- F+ ?/ f+ o
4 Q7 T$ `; ~( m3 j( B, c8 V: `}- |# S* M. `8 r/ Q
}
; n0 j; |4 t3 n6 ]- \}
' v4 J& y- l& h+ }3 p9 |% M7 h O& l
' N y4 _4 @! K$ ^. C
[url=]
[/url]
. B% L! k( f0 b- G0 G9 D2 k; M1 M \0 `% E5 \/ u" D2 d
, x, U. E6 d7 [7 u, n
# _5 l5 T0 O0 \5 J1 M2 j+ R3 @( g5 c- i( e! ~
网上据称这组TSP数据的最好的结果是 15404 ,上面的程序我刚才试了几次最好一次算出了15402.341,但是最差的时候也跑出了大于16000的结果。
我这还有一个版本,设置种群规模为1000,迭代5000次可以算出15408.508这个结果。源代码在文章最后可以下载。
0 D: D, t$ y/ j% u
总结一下使用AForge.Genetic解决问题的一般步骤:
(1) 定义适应函数类,需要实现IFitnessFunction接口
(2) 选定种群规模、使用的选择算法、染色体种类等参数,创建种群population
(3)设定迭代的最大次数,使用RunEpoch开始计算
! x: i' ^- R+ } [- o' T