我有一个计划生成任务,根据this question,可以借助遗传算法解决。
我经历了谷歌搜索,发现了许多非常有用的文献和两个“Hello World!”例子。到目前为止,我已经尝试将它们翻译成php,并重新封装以使代码可以重用于我未来的任务。
以下是C++
和Java
上的示例链接(抱歉,最后一个是俄语版,但仍然可以使用此代码)。
这是我的实施:
<?php
abstract class Creature
{
protected $fitness;
public function __construct()
{
$this->fitness = 0;
}
public function getFitness()
{
return $this->fitness;
}
abstract public function calculateFitness();
public function compareTo($creature)
{
return $this->fitness - $creature->fitness;
}
abstract public function mateWith($creature);
abstract public function mutate();
}
abstract class Population
{
protected $creatures;
protected $generation;
public function __construct()
{
$this->creatures = array();
$this->generation = 1;
$this->populate();
}
public function __destruct()
{
unset($this->creatures);
}
public function get($index)
{
return isset($this->creatures[$index]) ? $this->creatures[$index] : null;
}
public function getCount()
{
return count($this->creatures);
}
public function getGeneration()
{
return $this->generation;
}
abstract protected function populate();
public function sort($order = SORT_ASC)
{
switch($order)
{
case SORT_ASC:
$fn = function($c1, $c2){ return $c1->compareTo($c2); };
break;
case SORT_DESC:
$fn = function($c1, $c2){ return $c2->compareTo($c1); };
break;
default: return false;
}
return usort($this->creatures, $fn);
}
public function select(array $params)
{
$result = false;
if(isset($params['top']))
{
$length = round(abs($this->getCount() * $params['top']) / 100);
$this->creatures = array_slice($this->creatures, 0, $length);
$result = true;
}
if(isset($params['fn']) && is_callable($params['fn']))
{
$this->creatures = array_filter($this->creatures, $params['fn']);
$result = true;
}
return $result;
}
public function breed()
{
$candidates = $this->creatures;
shuffle($candidates);
$candidates = array_chunk($candidates, 2);
$result = 0;
foreach($candidates as &$pair)
{
if(count($pair) < 2)continue;
list($mother, $father) = $pair;
$children = $mother->mateWith($father);
$result += count($children);
$this->creatures = array_merge($this->creatures, $children);
}
$this->generation++;
return $result;
}
}
class HWCreature extends Creature
{
protected $string;
protected function randChar()
{
return chr(rand(0, 255));
}
protected function fill()
{
$length = strlen(Algorithm::TARGET);
for($i = 0; $i < $length; $i++)
{
$this->string .= $this->randChar();
}
}
public function __construct($fill = true)
{
parent::__construct();
$this->string = '';
if(!$fill)return;
$this->fill();
$this->calculateFitness();
}
public function __toString()
{
return $this->string;
}
public function calculateFitness()
{
$length = strlen($this->string);
$target = Algorithm::TARGET;
for($i = 0; $i < $length; $i++)
{
$this->fitness += abs(ord($this->string[$i]) - ord($target[$i]));
}
}
public function mateWith($creature)
{
$length = strlen(Algorithm::TARGET) - 1;
$place = rand(0, $length);
$child1 = new self(false);
$child1->string = substr($this->string, 0, $place) . substr($creature->string, $place);
$child1->mutate();
$child1->calculateFitness();
$child2 = new self(false);
$child2->string = substr($creature->string, 0, $place) . substr($this->string, $place);
$child2->mutate();
$child2->calculateFitness();
return array($child1, $child2);
}
public function mutate()
{
if(rand(1, 100) > Algorithm::MUTATION_RATE)return;
$char = $this->randChar();
$length = strlen(Algorithm::TARGET);
$place = rand(0, $length - 1);
$this->string = substr_replace($this->string, $char, $place, 1);
}
}
class HWPopulation extends Population
{
protected function populate()
{
for($i = 0; $i < Algorithm::POPULATION_SIZE; $i++)
{
$this->creatures[] = new HWCreature();
}
}
}
class Algorithm
{
const POPULATION_SIZE = 100; // 1000 in my original test
const ELITE_RATE = 50; // %
const MUTATION_RATE = 25; // %
const MAX_GENERATIONS = 1000;
const TARGET = 'Hello World!';
protected $population;
public function __construct()
{
$this->population = new HWPopulation();
}
public function __destruct()
{
unset($this->population);
}
public function __invoke()
{
do
{
$generation = $this->population->getGeneration();
$representer = $this->population->get(0);
echo sprintf(
'gen %d > %s',
$generation, $representer
),
'<br>',
PHP_EOL;
if($representer == self::TARGET)break;
$selector = array('top' => self::ELITE_RATE);
$this->population->sort();
$this->population->select($selector);
$this->population->breed();
}
while($generation < self::MAX_GENERATIONS);
}
}
$algorithm = new Algorithm();
$algorithm();
unset($algorithm);
?>
但是,我在带有i7 @ 2.4 GHz CPU的16Gb RAM机器上的结果是:
...
gen 739 > HfkkoWotlc!
gen 740 > HfkkoWotlc!
gen 741 > HfkkoWotlc!
gen 742 > HfkkoWotlc!
gen 743 > HfkkoWotlc!
gen 744 > HfkkoWotlc!
gen 745 > HfkkoWotlc!
Fatal error: Maximum execution time of 30 seconds exceeded in {script} on line 126
所以,看起来效率极低。我相信,这个问题可能出现在选择或育种策略中......而我在那里完全迷失了。
任何人,请解释一下,为什么会发生这种情况?另外,我只是通过交配精英基因/生物组来做错事吗?
任何帮助将不胜感激。
答案 0 :(得分:1)
至少为了调试/测试,您可能需要长时间运行算法,因此您应该在php.ini中增加max_execution_time
的值(或使用set_time_limit
函数)。
您的代码中的术语似乎有些混乱。从简短的一瞥看,你似乎没有实行精英主义。你似乎有truncation selection。以这种方式选择父母是错误的吗?嗯,它通常是次优的,因为它完全抛弃了较弱的候选者,虽然它们本身不可行,但可能含有可能有助于最终解决方案的遗传物质。在这个简单的例子中,它可能并不重要,但总的来说,你可能会发现fitness-proportionate selection strategy,例如轮盘赌选择,更有效。这种策略有利于更强大的个体,但允许选择较弱的候选人作为父母。
如果你想实施精英主义,你应该将未经修改的精英候选人复制到下一代,然后通过选择当前整个一代(包括精英个体)的父母来培育那一代的其他人。通过精英主义保留的候选人百分比应该是5%左右(你可以尝试找到最佳比例)。
其他一些观察结果: