Genetics Dot Net Two - 自适应编程






4.83/5 (18投票s)
2006年10月30日
22分钟阅读

85402

1598
了解使用遗传算法的自适应编程。
引言
该项目是 Visual Studio C# Express Edition 编译器和 SharpDevelop 1.x - 2.0 编译器联合开发的成果。原因是它们都有各自的缺点,而且个人来说,我无法长时间舒适地使用它们中的任何一个,因为它们以不同的方式让我感到恼火。然而,这只是一个个人问题,从技术角度来看,与 SharpDevelop 相比,Visual C# 中的 GUI 开发非常出色。此外,尽管 SharpDevelop 的调试器速度较慢,但它允许您正确调试线程,而 Visual C# 调试器则无法做到这一点。归根结底,SharpDevelop 仍在开发中,因此可以随着时间的推移得到改进,而 Visual C# 的任何新版本都可能会专注于微软明年试图推广的任何新技术。
从一位拥有 11 年经验的开发者的角度来看,为了追求更轻松、更快速,开发工具本身却倒退了一大步,这真是一个令人沮丧的局面。再次建议检查两个编译器是否会出现 bug,因为您不能再相信工具告诉您的内容了。所以,对于那些刚开始编程的人来说:欢迎来到 90 年代初期的编程风格。
好了,抱怨够了,言归正传。对于刚接触遗传算法的人来说,这是一个非常糟糕的起点。请先阅读:Genetics Dot Net - The Basics,因为那篇文章中提到的任何内容都不会在这里重复。正如上一篇文章的结论所述,这次我们将研究自适应编程。不过,我首先需要澄清一下它的含义。
自适应编程
在其著作《遗传算法在搜索、优化和机器学习中的应用》的第 2 页,David E. Goldberg 说:
"鲁棒性对人工智能系统意味着很多。如果人工智能系统能够更具鲁棒性,昂贵的重新设计就可以减少或消除。如果能实现更高水平的适应性,现有系统就可以更长久、更好地执行其功能。人工智能系统的设计者——无论是软件还是硬件,无论是工程系统、计算机系统还是商业系统——都只能惊叹于生物系统的鲁棒性、效率和灵活性。自修复、自导航和繁殖的特性在生物系统中是规则,而在最复杂的人工系统中却几乎不存在。"
很简单。那么,从编程的角度来看,我们要做的是开发一个系统,让代码能够在限制范围内,根据环境变化及其主要任务的变化进行更改和适应。当然,在代码层面,我们必须能够应对在物理层面几乎会遇到的各种变化——即,任何资源的耗尽都不是一个选项——同时还要保留程序的以目标为导向的特性。也就是说,我们不能允许代码的灵活性达到如此广泛的程度,以至于我们难以实现程序的最初目标。
首先,我应该解释一下程序本身,以便人们能够理解它运行时发生的情况。
程序
程序的设计(在页面顶部配图)是一个相当简单的算法在迷宫中寻找出路。我选择这个是因为这个想法很容易被人理解,而且它提供了许多视觉线索来表明正在发生什么。这样,即使是不一定理解算法工作原理的人,也能看到他们试图实现的目标。
GUI 本身分为三个部分,地图部分显示了正在发生的事情的视觉表示。请注意,这并非旨在进行完整表示。我们使用的是标准的 Windows 图形,如果我们不使用 DirectX,就根本无法显示所有正在发生的事情。然而,这样做也毫无意义,因为即使您可以看到所有发生的事情,您仍然无法处理这些信息。这是因为事情太多了,任何图形很可能只会是一片模糊的色彩。解决方案是选择每次运行中的一个遗传字符串进行显示。这样,我就可以在不使 Windows 图形系统过载的情况下提供视觉表示——至少在我的系统上是如此。尽管如此,我不知道程序在低于 64 位 4GHz AMD 的机器上是如何运行的,尽管人们无疑会抱怨如果它运行得很糟糕。
GUI 的第二部分是文本框,用于报告代码的进度。最后一部分是选项卡,用于启动和停止每个示例以及设置遗传算法的基本参数。
地图
地图有两种类型:简单地图,
主要用于开发初期,以及普通或标准地图,
您可以看到,每张地图都需要一个起点和一个终点。之后,它们可以像您想要的那么简单或复杂。请记住,地图的复杂性越高,算法完成工作所需的时间就越长。因此,尽管简单地图可能需要几秒钟才能完成,但地图三可能需要很长时间。
地图从地图选项卡加载
只需选择您要加载的地图,然后点击加载地图按钮。在启动算法时,此按钮将被禁用,以便您无法在中途更改地图。一旦算法停止,它就会重新启用。
地图本身是每个方格的简单 XML 结构
您可以看到,每个方格都包含方格标识符以及该方格是否代表地图起点或终点的值。建议每张地图只有一个起点和一个终点;代码中没有处理更多的情况。按照历代编程手册的传统,我们只需说明拥有多个起点或多个终点方格将导致未定义的行为。项目中附带一个名为 MapTemplate.xml 的文件。它将所有设置都设为 false,因此如果您想创建自己的地图,它就是一个干净的起点。或者,您也可以修改现有地图。
图形界面
画布本身是一个单一窗口,分为方格,每个方格控制着窗口自身的一部分。实际上,它们在行为上就像单独的窗口控件,尽管它们没有作为单独控件的开销。相反,它们充当画布控件本身的绘图区域。这在 GeneticsBoard.cs 文件中有所展示,在 InitialiseBoard
函数中,我们有以下代码
GetHashTable.Add( "A8", new GeneticsSquare( SquareWidth,
SquareHeight, 0, 0, "A8" ) );
GetHashTable.Add( "A7", new GeneticsSquare( SquareWidth,
SquareHeight, 0, SquareHeight, "A7" ) );
GetHashTable.Add( "A6", new GeneticsSquare( SquareWidth,
SquareHeight, 0, SquareHeight * 2, "A6" ) );
GetHashTable.Add( "A5", new GeneticsSquare( SquareWidth,
SquareHeight, 0, SquareHeight * 3, "A5" ) );
您可以看到,每个方格都被添加到画布类中的哈希表中,理论上,方格的名称/位置用作访问它的键。然而,实际上,我们很少以如此受控的方式处理代码中的事物,以至于我们始终确切地知道我们在哪里。因此,实际上,哈希表倾向于用作标准数组。这是因为遗传算法决定其去向的方式。它不是根据方格的画布位置来决定的,而是生成一个方向列表,例如左、下、右、左、上左,然后看看它们在画布上通向哪里。反过来,这就是为什么任何人都可以为该项目设计地图的原因。算法不在乎它在画布上的起始位置或穿过画布的有效路径是什么;这一切都在窗体本身的实现代码中决定。
您可以看到,地图由绿底上的金色线条组成,三根电线或字符串用于表示正在发生的事情。工作电线是棕色和白色的电线
这称为当前尝试字符串,所有三个算法都使用它来显示其进度。正如上面所述,这只是显示了整个运行过程中遗传字符串集的一部分。第二根电线是绿黄色的字符串
这是当前解决方案字符串,用于表示已完成的解决方案,或者在没有已完成解决方案的情况下,表示当前最佳可用解决方案。这并不完全准确,因为它根据哪个字符串走得最远而没有撞到墙壁来工作。这意味着,实际上,一个字符串走左、右、左、右、左、右、右、左、左的字符串比一个字符串走左、左、左的字符串旅行得更远。最后一根电线是红蓝色字符串
这是上一个解决方案字符串。它仅用于完整且有效的到达终点的路径。正如您稍后将看到的,有一种情况是当前解决方案电线可能认为它已到达终点,但路径无效。
输出部分
输出框用于显示程序在任何给定时间正在发生的事情,其想法是它可以用来查看代码中正在发生的事情。实际上,这往往并非如此。这是因为事情太多了,即使是第一个示例代码的简短运行,在使用了“保存输出”选项时也会产生一份相当于大约 250 页 A4 纸的文档。这还不包括您开始修改代码中的选项。实际上,它更像是指示一切是否正常工作的指示器。这是因为,当框获得焦点时,会看到正在发生的事情。如果很长时间都没有向框中写入任何内容,那么很可能出了严重问题。
在 Form1.cs 文件的顶部有一个部分,
/// progress print out stuff
///
private bool bPrintCurrentAttemptFinds = false;
private bool bPrintTimerMessage = false;
private bool bPrintRunMessages = true;
private bool bPrintGenerationMessages = true;
private bool bPrintSampleDebugString = true;
private bool bPrintFullBestString = true;
private bool bDebugProgress = false;
private bool bDebugSetValid = false;
在 GeneticsPath.cs 文件中也有一个类似但较小的部分。这也将允许输出跟随算法在整个运行函数中的进度。打开所有这些选项将导致性能大幅下降,并在保存输出时生成数千页长的信息文件。
选项部分
页面底部有五个选项卡。我们已经看到其中一个:上面的地图选项卡。第一个选项卡是遗传选项卡
代数框控制代数数量,以及每代周期或运行次数。如果这些对您来说毫无意义,请参考上面提到的文章。延迟框用于控制每个示例的循环开始时。这里的想法是,您可以停止程序执行一两秒钟,从而为绘图函数提供处理器时间来完成其工作。
最后三个选项卡是示例选项卡,用于控制每个示例。在这些选项卡上可以更改的唯一设置是初始字符串大小和种群大小。再次,这些都在前面的文章中进行了详尽的描述,尽管不得不说,实际上并没有必要更改任何算法设置。
示例
到此为止,我们对程序工作原理的介绍就结束了。现在,我们可以通过直接查看示例及其工作方式来深入了解这个项目的核心内容。应该注意的是,虽然示例三显然是最有趣的示例,但按顺序浏览示例是一个好主意,因为示例二和示例三是基于示例一中使用的想法的逻辑进展。
概念问题
该程序在开发期间,我给一个不是程序员的朋友看,只是为了让他知道我在做什么。我提到这一点的原因是,他认为代码很愚蠢,因为它一再撞到死胡同。所以我不得不解释说,程序的运作方式是它没有意识到的事实,即它从外表上看是在穿越迷宫。它只是生成了随机数量的选项,在这些情况下,碰巧是四个方向之一。对于找到迷宫出路的代码来说,它试图找到出路的地图没有区别。事实上,它完全有可能在它运行的每张地图上生成完全相同的方向,并且在某个时候,Validation
函数是唯一决定这些方向准确性的函数。这一点在思考遗传算法的工作方式时非常重要,因为当涉及到实际生成选项时——更准确地说,是遗传数据本身——遗传算法只是关心对给定选项的近乎随机的选择。也就是说,除非从一开始就用更具体的环境信息对其进行编程,例如在工程项目中,那样的话“A”跟随“B”就没有意义了。因此,算法从根本上设计为忽略这种可能性。
扩展遗传数组算法(示例一)
定义
示例一的定义是一个遗传数组,它根据程序的要求自动扩展。如果您已经运行了示例代码,您将看到每个示例都需要 20 的初始字符串大小。这是遗传算法字符串的起始长度。您可能会注意到 GUI 没有设置上限。这只能在初始化示例时在代码中设置
gpExampleOne.SetExandingGeneticsArray( 100,
( int )exampleOneStartStringSize.Value, 10 );
这被称为告诉遗传代码我们正在使用一个动态算法,稍后将在代码中对其进行增长。该函数的定义是
public void SetExandingGeneticsArray( int maxLength,
int startLength, int incrementLength )
您可以看到数组的最大长度设置为 100。初始字符串大小设置为 GUI 中的设置,默认为 20,增量长度设置为 10。此处设置的最大长度对于我们使用的地图大小来说是足够的,增量长度是一个不错的整数。
在所有方面,该算法的工作方式都与任何标准遗传算法一样。它通过运行函数,可能会或可能不会对某个遗传字符串应用变异。主要区别出现在运行函数结束时,您会遇到这段代码
if( IsFixedLength == false && nMaxSize == CurrentLength )
{
这测试我们是否正在使用标准遗传算法。如果不是,它会测试我们是否已达到当前最大尺寸。请注意,这不是绝对最大尺寸;这是初始字符串长度加上我们已经进行的增量长度值。将其与当前长度进行比较。我应该明确,当前长度是衡量字符串成功穿越迷宫的距离。遗传字符串内的所有项都一直使用。这只是衡量从字符串开头开始的所有有效移动。
然后代码遍历种群,将新的 GeneticsPathItems
添加到字符串中。代码的基本构成是每个 GeneticPath
包含一个 GeneticsStrings
数组,每个种群项一个。每个 GeneticsString
包含一个 GeneticsPathItems
数组,这些是迷宫路径的实际方向信息片段。
主循环
示例一的初始化完成后,我们进入示例的主循环,其中
>while( bStop == false ) {
我们进行一些更通用的控制操作,然后调用
SetValidPaths( gpExampleOne );
此调用将当前的 GeneticsPath
对象传递给 SetValidPaths
函数。然后,该函数遍历 GeneticsPath
数组中包含的每个 GeneticsString
,并检查每个移动指令与当前加载地图的有效性。然后,我们进行一些进一步的检查和调试操作,然后到达
ExampleOneFitness( gpExampleOne, tExampleOne, bStopThreadOne );
if( bFinishIsSet == true )
{
tsTextBox.AppendTextWithColour( "At generation number "
+ nGenerationCount.ToString() + " run number "
+ nRunCount.ToString() + " the algorythm found the finish.",
Color.Red );
geneticsBoard1.SetCurrentSolution( gpsCurrentAttemptString );
OnGuiTimer( this, new EventArgs() );
bStop = true;
break;
}
在这种情况下,适应度函数只是检查我们是否有一个字符串,它拥有一系列到达地图终点且不被阻塞的有效方格,这只在示例三中变得相关。如果我们找到一个到达终点方格的方向字符串,则 bFinishIsSet
值将被设置为 true
,我们在下一行代码中进行检查。在示例一中,我们然后将当前解决方案设置为当前尝试,绘制它,然后退出示例。下一行很重要
gpExampleOne.Run();
这调用 GeneticsPath
Run 函数,该函数对每个字符串执行遗传算法代码。应该注意的是,这里的所有示例都使用单点交叉和轮盘赌选择。如果这对您来说毫无意义,请阅读之前的文章。下一行是
if( nGenerationCount == gpExampleOne.NumberOfGenerations )
{
if( bPrintGenerationMessages == true )
{
tsTextBox.AppendTextWithColour(
"Number Of Generations Reached, Halting Algorythm",
Color.BlueViolet );
}
bStop = true;
break;
}
这只是检查我们运行的代数是否等于我们应该运行的总代数。如果是,我们退出循环,算法很可能未能找到解决方案。最后重要的代码段是
if( nRunCount == gpExampleOne.NumberOfCyclesPerGeneration )
{
if( gpsCurrentAttemptString.HasReachedFinish
== true && gpsCurrentAttemptString.LengthTravelled
>= gpsMaxString.LengthTravelled )
{
geneticsBoard1.ResetPreviousSolution();
geneticsBoard1.SetPreviousSolution( gpsMaxString );
gpsPreviousSolutionString = gpsMaxString;
}
if( bPrintGenerationMessages == true )
{
tsTextBox.AppendTextWithColour(
"Number Of Cycles Run For Generation " +
nGenerationCount.ToString()
+ ", Reinitialising Genetics Array", Color.BlueViolet );
}
gpExampleOne.Initialise();
gpExampleOne.SingleCrossOverPoint = gpExampleOne.StartingLength /2;
if( gpExampleOne.SingleCrossOverPoint == 0 )
Debug.WriteLine(
"The Single Point CrossOver has been
screwed up in run after the initialisation" );
nGenerationCount++;
nRunCount = 0;
}
这段代码检查我们是否运行了足够次数的周期来算作完整的代数。再次,如果这对您来说毫无意义,请参考之前的文章。我们首先检查上一轮是否有任何一个达到了终点方格。如果有,我们将上一个解决方案字符串重置为终点并显示它。然后,我们重新初始化 GeneticsPath
对象,并将单点交叉点重置回开头。这是因为它会移动,因为遗传数组在运行函数期间会扩展。最后,我们增加代数计数,并将新一代的运行计数设置为零。
实际运行
当第一个示例运行时,它看起来主要像这样
这说明了当前尝试字符串,显示了它在尝试找到到达终点的方法时当前运行的最大字符串。当它确实找到终点时,它将设置上一个解决方案字符串并如下显示
改进选项算法(示例二)
定义
示例二的定义是从示例一开始逻辑发展的,因为一旦我们找到了给定地图的终点,我们就会开始想知道是否可以做得更好。示例二将采用运行第一个示例代码提供的解决方案,然后再次运行算法,看看是否能找到一种地图的路径——对于这个示例来说,它的标准是比之前保存的解决方案更短。示例二的设置过程几乎与示例一相同,除了代码
StringBuilder strMapName = new StringBuilder( strMap );
strMapName = strMapName.Replace( ".xml", "ExampleTwo.sav" );
if( File.Exists( strMapName.ToString() ) == true )
{
LoadPreviousSolutionString( strMapName.ToString() );
}
else
{
while( RunInitialSearch( gpExampleTwo,
tExampleTwo, ref bStopThreadTwo ) == false );
SavePreviousSolutionString( strMapName.ToString() );
Settings( gpExampleTwo );
GeneticsPathString.InitialChromosomeLength
= ( int )exampleTwoStartStringSize.Value;
gpExampleTwo.PopulationSize = ( int )exampleTwoPopulationSize.Value;
/// clear the board
///
geneticsBoard1.ResetCurrentAttempt();
geneticsBoard1.ResetCurrentSolution();
gpExampleTwo.Initialise();
Monitor.Enter( tExampleTwo );
bFinishIsSet = false;
Monitor.Exit( tExampleTwo );
}
geneticsBoard1.SetPreviousSolution( gpsPreviousSolutionString );
这里的区别在于,对于示例二,我们正在尝试改进算法的上一次运行。所以,首先,我们必须加载算法。它存储为 XML 文件,可以使用任何 XML 查看器查看。该项目的保存文件具有 .sav 扩展名,以便在您要加载地图时不会出现在选项中。
除此之外,唯一明显的区别是 RunInitialSearch
函数。这是对初始示例一主循环的重写,并被分离成自己的函数。目前,示例二算法只会尝试一次运行算法以找到更快的路径。这意味着,如果运行不成功,则需要手动重新启动。将此重写为算法继续运行直到找到更快解决方案的功能,也不会太具挑战性。
主循环
示例二的主循环与示例一的代码基本相同。主要例外是我们现在使用上一个解决方案字符串来显示上一次运行找到的解决方案。完成后,当前解决方案字符串用于显示当前找到的最佳解决方案。测试解决方案之间的对比
if( gpsCurrentAttemptString.LengthTravelled
<= gpsPreviousSolutionString.LengthTravelled )
{
gpsPreviousSolutionString = gpsCurrentAttemptString;
SavePreviousSolutionString( strMapName.ToString() );
geneticsBoard1.SetCurrentSolution( gpsCurrentAttemptString );
OnGuiTimer( this, new EventArgs() );
bStop = true;
break;
}
这只有在当前字符串比算法上一次运行时找到的短时,才将上一个解决方案字符串设置为当前尝试字符串。当到达运行结束时,我们现在有代码
if( bFinishIsSet == false )
{
nMaxSize = 0;
gpsTest = null;
for( int i=0; i<gpExampleTwo.PopulationSize; i++ )
{
gpsTest
= ( GeneticsPathString )gpExampleTwo.GeneticsArray[ i ];
if( gpsTest.LengthTravelled >= nMaxSize )
{
nMaxSize = gpsTest.LengthTravelled;
gpsMaxString = gpsTest;
}
}
gpsCurrentSolutionString = gpsMaxString;
}
这会将当前解决方案字符串设置为当前运行中找到的最大字符串。
实际运行
当示例二首次启动时,它看起来像这样
这显示了上一次运行找到的解决方案和算法当前正在尝试寻找终点的尝试。并且当前(最佳)尝试。
这张图片显示示例二显示了上一次运行的解决方案、它当前寻找终点的尝试以及迄今为止找到的最佳当前解决方案。这就是示例二成功找到更好解决方案时的样子
即使当前解决方案在技术上看起来更长,但当前算法中没有任何代码可以阻止字符串来回覆盖它们已经覆盖的区域。所以成功的运行是一个在更少的步数内到达终点的字符串,无论它看起来是否如此。
终结者算法(示例三)
定义
第三个示例——事实上,整个项目——是《终结者》电影的结果。具体来说,我指的是阿诺德终结者在钢铁厂场景中,以及更新的液态金属终结者用钢筋从他背后刺穿他的场景。然后是一个简短的序列,我们看到阿诺德重新路由他的电路以找到电源。所以请注意:这个项目是当你观看科幻电影并想,“我想知道你怎么做到的”的一个典型例子。这是其中一种解决方案。
设置示例一的代码与设置示例二的代码之间的区别仅在于阻塞代码的结构。即
string strSquare = geneticsBoard1.GetStartSquare();
GeneticsSquare gsSearchSquare
= ( GeneticsSquare )geneticsBoard1.GetSquare( strSquare );
GeneticsSquare gsTempSquare = null;
GeneticsPathItem gpiPathItem = null;
foreach( DictionaryEntry dicEnt in geneticsBoard1.GetHashTable )
{
gsTempSquare = ( GeneticsSquare )dicEnt.Value;
if( gsTempSquare.Blocked == true )
{
gsTempSquare.Blocked = false;
gsTempSquare.DrawBlocked = false;
}
}
if( geneticsBoard1.InvokeRequired == true )
{
InvalidateCallback d = new InvalidateCallback( Invalidate );
geneticsBoard1.Invoke( d, new object[]{} );
UpdateCallback f = new UpdateCallback( Update );
geneticsBoard1.Invoke( f, new object[]{} );
}
else
{
geneticsBoard1.Invalidate();
geneticsBoard1.Update();
}
for( int i=0; i<nBlockSquare; i++ )
{
gpiPathItem
= ( GeneticsPathItem )gpsPreviousSolutionString.GeneticsString[ i ];
if( gpiPathItem == null )
{
MessageBox.Show( "Error, trying to get item "
+ i.ToString() +
" from the previous solution string" );
return;
}
switch( gpiPathItem.Direction )
{
case "Up": gsTempSquare =
( GeneticsSquare )geneticsBoard1.GetSquareAbove(
gsSearchSquare.Identifier ); break;
case "Right": gsTempSquare
= ( GeneticsSquare ) geneticsBoard1.GetSquareToRight(
gsSearchSquare.Identifier ); break;
case "Down": gsTempSquare
= ( GeneticsSquare )geneticsBoard1.GetSquareBelow(
gsSearchSquare.Identifier ); break;
case "Left": gsTempSquare
= ( GeneticsSquare )geneticsBoard1.GetSquareToLeft(
gsSearchSquare.Identifier ); break;
}
if( gsTempSquare != null )
{
gsSearchSquare = gsTempSquare;
}
}
gsSearchSquare.Blocked = true;
gsSearchSquare.DrawBlocked = true;
首先,我们移除上一次运行中设置的任何阻塞。然后我们重新绘制画布以清理它,然后再设置一个新的我们希望能够绕过的阻塞。由于没有任何东西阻止字符串来回穿过同一个方格,因此理论上可能——考虑到地图的设计方式——会放置一个算法根本无法绕过的阻塞。
主循环
示例三的主循环与示例二的主循环几乎相同。两者之间的主要区别在于适应度函数中处理的阻塞。由于这根本不适用于早期的示例,因此它的代码不会妨碍它们的运行。
实际运行
开始时,示例三的运行看起来像
一旦开始运行,并且也包含当前解决方案,它看起来就像这样
最后——是的,这花了几个运行——阻塞在每个示例的代码运行的开头重置
结论
这差不多涵盖了我目前将要使用遗传算法的内容。我想下次我将坚持一些简单的事情,比如 for 循环是如何工作的。
主要参考资料
-
Mat Buckland , ( 2002 ) AI Techniques For Game Programming, Premier Press Game Development Series.
-
David E. Goldberg, ( 1989 ) Genetic Algorithms In Search Optimization And Machine Learning, Addison Wesley.
-
Wolfgang Banzhaf et al, ( 1998 ) Genetic Programming An Introduction, Morgan Kaufmann.
次要参考资料
-
Tom Archer ( 2001 ) C# 内幕,Microsoft Press
-
Jeffrey Richter ( 2002 ) Applied Microsoft .NET Framework Programming, Microsoft Press
-
Charles Peltzold ( 2002 ) 使用 C# 编程 Microsoft Windows,Microsoft Press
-
Robinson 等 ( 2001 ) 专业 C#,Wrox
-
William R. Staneck ( 1997 ) Web Publishing Unleashed Professional Reference Edition, Sams.net
-
Robert Callan, ( 1999 ) The Essence Of Neural Networks, Prentice Hall
-
Timothy Masters ( 1993 ) Practical Neural Network Recipes In C++, Morgan Kaufmann ( Academic Press )
-
Melanie Mitchell ( 1999 ) An Introduction To Genetic Algorithms, MIT Press
-
Joey Rogers ( 1997 ) Object-Orientated Neural Networks in C++, Academic Press
-
Simon Haykin ( 1999 ) Neural Networks A Comprehensive Foundation, Prentice Hall
-
Bernd Oestereich ( 2002 ) Developing Software With UML Object-Orientated Analysis And Design In Practice Addison Wesley
-
R Beale & T Jackson ( 1990 ) Neural Computing An Introduction, Institute Of Physics Publishing
-
Bart Kosko ( 1994 ) 模糊思维,Flamingo
-
Buckley & Eslami ( 2002 ) 模糊逻辑与模糊集导论,Physica-Verlag
-
Steven Johnson ( 2001 ) Emergence, The Penguin Press
-
John H. Holland ( 1998 ) Emergence From Chaos To Order, Oxford University Press
-
Earl Cox ( 1999 ) 模糊系统手册,AP Professional
-
Mark Ward ( 1999 ) Virtual Organisms, Pan
-
Bonabeau, Dorigo, Theraulaz ( 1999 ) Swarm Intelligence From Natural To Artificial Systems, Oxford University Press
-
Masao Mukaidono, Fuzzy Logic For Beginners ( 2002 ) World Scientific Publishing
-
Deborah Gordon, Ants At Work ( 1999 ), W W Norton
-
Gigerenzer, Todd & ABC Research Group ( 1999 ) Simple Heuristics That Make Us Smart, Oxford University Press
-
Rodney A. Brooks ( 1999 ) Cambrian Intelligence, MIT Press
-
Winfred F. Hill, ( 1964 ) Learning A Survey Of Psychological Interpretations, University Paperbacks.
-
M. Tim Jones, ( 2003 ) AI Application Programming, Charles River Media
历史
- 2007年5月16日 - 文章已编辑并发布到 CodeProject.com 文章主库