其背后的思想很简单:给定一个数字和几个参数,生成一个方程式,得出该数字作为解。我已经实现了
*
,/
,+
,-
和^
运算符。 这个世界充满了形式为
x + (y - z)
的方程式,其中值是随机生成的(以后我可能会随机生成这些运算符)。实现:将终止其结果不在目标解决方案的预定范围内的方程。
新一代由变异方程和子代组成。父母已不再是新一代。
只剩下一代人时,它会偶尔发生变异,直到他超出可接受的范围。
这会引起一个问题:这是好的方法还是应该改用其他方法?
Program
class Program
{
static void Main(string[] args)
{
new Program();
Console.Read();
}
private static void PrintTree(GeneticTree tree)
{
var output = string.Format("{0}: {1:0}{2, 15}[{3}]", tree.Name, tree.GetResult(), string.Empty, tree.GetDisplay());
Console.WriteLine(output);
}
private static void PrintWorld(World world)
{
Console.WriteLine("================================");
Console.WriteLine("Generation {0}", world.Generation);
foreach (var algorithm in world.Population)
{
PrintTree(algorithm);
}
}
public Program()
{
var world = new World(targetValue: 50, populationSize: 30, fitnessDelta: 100, mutationChance: 0.2d, minValue: -50, maxValue: 50);
do
{
PrintWorld(world);
} while (!world.AdvanceGeneration() && !world.Apocalypse);
if (world.Apocalypse)
{
Console.WriteLine("No suitable solution has been found with the given parameters");
}
else
{
PrintWorld(world);
Console.WriteLine("Possible solutions after {0} generation(s):", world.Generation);
foreach (var solution in world.WorkingAlgorithms)
{
Console.WriteLine("{0}: {1}", solution.Name, solution.GetDisplay());
}
}
}
}
World
public class World
{
private static readonly Random Random = new Random();
private readonly int _populationSize;
private readonly int _targetValue;
private readonly int _fitnessDelta;
private readonly double _mutationChance;
private readonly int _minValue;
private readonly int _maxValue;
public List<GeneticTree> Population { get; private set; }
public List<GeneticTree> WorkingAlgorithms { get; private set; }
public bool Apocalypse { get; private set; }
public int Generation { get; private set; }
public World(int targetValue, int populationSize, int fitnessDelta, double mutationChance, int minValue, int maxValue)
{
Population = new List<GeneticTree>();
WorkingAlgorithms = new List<GeneticTree>();
_targetValue = targetValue;
_populationSize = populationSize;
_fitnessDelta = fitnessDelta;
_mutationChance = mutationChance;
_minValue = minValue;
_maxValue = maxValue;
Initialize();
Generation = 1;
}
private void Initialize()
{
for (var i = 0; i < _populationSize; i++)
{
var tree = new GeneticTree( _minValue, _maxValue)
{
Name = "Algorithm " + i
};
Element ops = new AdditionOperator
{
LeftElement = new ValueElement(Random.Next(_minValue, _maxValue)),
RightElement = new SubtractionOperator
{
LeftElement = new ValueElement(Random.Next(_minValue, _maxValue)),
RightElement = new ValueElement(Random.Next(_minValue, _maxValue))
}
}; // x + (y - z)
tree.AddOperations(ops);
Population.Add(tree);
}
}
/// <summary>
/// Returns true when a solution has been found
/// </summary>
public bool AdvanceGeneration()
{
var newGeneration = new List<GeneticTree>(Population.Count);
// Add random roll to determine whether it should mutate or combine with another algorithm
for (int index = 0; index < Population.Count; index++)
{
var algorithm = Population[index];
if (Random.NextDouble() < _mutationChance)
{
algorithm.Mutate();
newGeneration.Add(algorithm);
}
else
{
var randomParent = Population.ElementAt(Random.Next(0, Population.Count));
var child = algorithm.Combine(randomParent);
newGeneration.Add(child);
}
}
Population = newGeneration;
++Generation;
return CheckFitness();
}
private bool CheckFitness()
{
var foundSolution = false;
for (int index = 0; index < Population.Count; index++)
{
var algorithm = Population[index];
var result = algorithm.GetResult();
if ((Math.Abs(result - _targetValue) < 0.1))
{
WorkingAlgorithms.Add(algorithm);
foundSolution = true;
}
else
{
if (Math.Max(result, _targetValue) - Math.Min(result, _targetValue) > _fitnessDelta)
{
Population.RemoveAt(index);
}
}
}
if (Population.Count == 0)
{
Apocalypse = true;
}
return foundSolution;
}
}
Element
public abstract class Element
{
public abstract double GetValue();
public abstract string GetDisplay();
public abstract List<Element> Children { get; }
}
Operators
public abstract class Operator : Element
{
}
public abstract class BinaryOperator : Operator
{
public Element LeftElement { get; set; }
public Element RightElement { get; set; }
protected abstract string GetBinarySpecificDisplay();
protected abstract double GetBinarySpecificValue(double leftValue, double rightValue);
public override double GetValue()
{
var left = LeftElement as ValueElement;
var leftValue = left != null ? left.Value : LeftElement.GetValue();
var right = RightElement as ValueElement;
var rightValue = right != null ? right.Value : RightElement.GetValue();
return GetBinarySpecificValue(leftValue, rightValue);
}
public override List<Element> Children
{
get { return new List<Element> { LeftElement, RightElement }; }
}
public override string GetDisplay()
{
return LeftElement.GetDisplay() + " " + GetBinarySpecificDisplay() + " " + RightElement.GetDisplay();
}
}
public class AdditionOperator : BinaryOperator
{
protected override string GetBinarySpecificDisplay()
{
return "+";
}
protected override double GetBinarySpecificValue(double leftValue, double rightValue)
{
return leftValue + rightValue;
}
}
public class SubtractionOperator : BinaryOperator
{
protected override double GetBinarySpecificValue(double leftValue, double rightValue)
{
return leftValue - rightValue;
}
protected override string GetBinarySpecificDisplay()
{
return "-";
}
}
public class DivisionOperator : BinaryOperator
{
protected override double GetBinarySpecificValue(double leftValue, double rightValue)
{
return leftValue / rightValue;
}
protected override string GetBinarySpecificDisplay()
{
return "/";
}
}
public class MultiplicationOperator : BinaryOperator
{
protected override string GetBinarySpecificDisplay()
{
return "*";
}
protected override double GetBinarySpecificValue(double leftValue, double rightValue)
{
return leftValue * rightValue;
}
}
public class ExclusiveOrOperator : BinaryOperator
{
protected override string GetBinarySpecificDisplay()
{
return "^";
}
protected override double GetBinarySpecificValue(double leftValue, double rightValue)
{
return (int) leftValue ^ (int) rightValue;
}
}
ValueElement
public class ValueElement : Element
{
public double Value { get; set; }
public ValueElement(double value)
{
Value = value;
}
public override double GetValue()
{
return Value;
}
public override string GetDisplay()
{
return Value.ToString(CultureInfo.InvariantCulture);
}
public override List<Element> Children { get { return null; } }
}
GeneticTree
public class GeneticTree
{
private const double MutationChance = 0.2d;
private readonly int _upperBoundary;
private readonly int _lowerBoundary;
private readonly static Random Random = new Random();
private readonly static Type[] BinaryOperations =
{
typeof(AdditionOperator),
typeof(SubtractionOperator),
typeof(DivisionOperator),
typeof(MultiplicationOperator),
typeof(ExclusiveOrOperator)
};
private bool _canStillSwap;
private Element _nodes;
public string Name { get; set; }
public int Depth { get; private set; }
public GeneticTree(int minValue, int maxValue)
{
_lowerBoundary = minValue;
_upperBoundary = maxValue;
}
public void AddOperations(Element element)
{
_nodes = element;
GetTreeDepth();
}
private void GetTreeDepth()
{
if (_nodes == null)
{
return;;
}
Depth = 1;
if (_nodes.Children != null)
{
GetTreeDepth(_nodes.Children);
}
}
private void GetTreeDepth(List<Element> children)
{
foreach(var child in children)
{
Depth++;
if (child.Children != null)
{
GetTreeDepth(child.Children);
}
}
}
public double GetResult()
{
return _nodes.GetValue();
}
public string GetDisplay()
{
return _nodes.GetDisplay();
}
public void Mutate()
{
_canStillSwap = true;
_nodes = InternalMutate(_nodes);
}
private Element InternalMutate(Element element)
{
if (!_canStillSwap)
{
return element;
}
if (MustMutate())
{
var valueElement = element as ValueElement;
if (valueElement != null)
{
return MutateValueElement();
}
var binaryElement = element as BinaryOperator;
if (binaryElement != null)
{
return MutateBinaryElement(binaryElement);
}
}
else
{
if (element.Children != null)
{
var binaryOperator = element as BinaryOperator;
if (binaryOperator != null)
{
var leftChild = binaryOperator.LeftElement;
var rightChild = binaryOperator.RightElement;
leftChild = InternalMutate(leftChild);
rightChild = InternalMutate(rightChild);
binaryOperator.LeftElement = leftChild;
binaryOperator.RightElement = rightChild;
}
}
}
return element;
}
private Element MutateValueElement()
{
_canStillSwap = false;
return new ValueElement(Random.Next(_lowerBoundary, _upperBoundary));
}
private Element MutateBinaryElement(BinaryOperator element)
{
var currentType = element.GetType();
var newType = BinaryOperations.Except(new[] { currentType }).OrderBy(x => Guid.NewGuid()).First();
var newElement = (BinaryOperator) Activator.CreateInstance(newType);
newElement.LeftElement = element.LeftElement;
newElement.RightElement = element.RightElement;
_canStillSwap = false;
return newElement;
}
private bool MustMutate()
{
return Random.NextDouble() < MutationChance;
}
public GeneticTree Combine(GeneticTree otherParent)
{
// We will assume both trees have the same layout and depth.
// For example:
// + *
// 5 - 3 ^
// 6 2 4 2
if (Depth != otherParent.Depth)
{
throw new ApplicationException("Trees are not similarly constructed");
}
var pivot = Depth / 2;
if (pivot <= 1)
{
return this;
}
if (_nodes.Children != null)
{
var binaryNodeMom = _nodes as BinaryOperator;
var binaryNodeDad = otherParent._nodes as BinaryOperator;
if (binaryNodeMom != null && binaryNodeDad != null)
{
var momLeftElement = binaryNodeMom.LeftElement;
var dadRightElement = binaryNodeDad.RightElement;
var tree = new GeneticTree(_lowerBoundary, _upperBoundary)
{
Name = "Nameless child"
};
var child = binaryNodeMom;
child.LeftElement = momLeftElement;
child.RightElement = dadRightElement;
tree.AddOperations(child);
return tree;
}
}
return this;
}
}
样本输出:
GitHub
那些想要玩转它或喜欢更舒适的查看方式的人,可以从我的GitHub获取代码。
#1 楼
您正在将算法分为两类:有效算法和非有效算法;这是相当Manichean。您应该尝试设计一种算法不能“起作用”但可以“比其他方法更好地接近预期结果”的东西。换句话说,不要试图找到“谁有效”,而是“谁更好”。 IMO最好的方法是获得一代人的所有结果,将它们进行比较以查看哪些结果更接近预期结果,哪些结果更接近预期结果。换句话说,您应该将算法称为“相对适应度”,以了解哪种解决方案比其他解决方案“相对更好”。这种相对适应度将帮助您生成一个新一代,具有以下步骤:
为每个算法分配一个生存率,相对于它的相对适应度(越接近预期结果,生存率越高)。
对于您种群中的每种算法,请获取一个介于0和1之间的随机数。
删除其随机数大于其生存百分比的算法。
保留下一代的生存算法。
通过创建算法并将剩余的算法组合起来,从而使新一代产品变得完整。
如果这样做,那么您几乎应该能够始终保持各代产品之间的最佳算法,但您还将保留其他一些突变和/或组合可能无法使用的可能会产生比您当前最好的更好的方法。此过程“您可能会拥有出乎意料的才能”对于避免本地极端情况很有用。
#2 楼
代码闻起来不要在构造函数中做所有工作
这段代码看起来像是有用的:
static void Main(string[] args)
{
new Program();
Console.Read();
}
构造函数中的所有代码都可以放在
Main
中。我们将代码从Main
移至实例类的原因是,我们可以使用多种配置来运行它们。例如,这里没有办法与另一个Program
一起运行World
。可以更改它,以便代替,您可以轻松地找出要在Program.cs中进行的伴随更改:/>以下可能是接口:
static void Main(string[] args)
{
var world = new World(
targetValue: 50,
populationSize: 30,
fitnessDelta: 100,
mutationChance: 0.2d,
minValue: -50,
maxValue: 50);
new Program(world).Run(); // I can reuse `Run` method elsewhere now
Console.Read();
}
不要对代码的可用性施加不必要的限制。
顺便说一下,这是在C ++中定义接口的方式;如果这是C ++使用的习惯,则应在编码C#时摆脱它。
如果返回类型是集合或可枚举,请不要返回
null
。这是个坏消息:
public abstract class Element
{
public abstract double GetValue();
public abstract string GetDisplay();
public abstract List<Element> Children { get; }
}
public abstract class Operator : Element
{
}
实际使用的唯一位置是:
List<Element> Children { get { return null; } }
如果您返回一个空列表,则以上两个null检查都不是必需的。
不要对未使用的变量进行null检查
我说“ [
Element.Children
]实际上被使用了”,因为在其他情况下会对其进行空检查然后不使用它!此处: private void GetTreeDepth()
{
// ... SNIP
if (_nodes.Children != null)
{
GetTreeDepth(_nodes.Children);
}
}
private void GetTreeDepth(List<Element> children)
{
foreach(var child in children)
{
Depth++;
if (child.Children != null)
{
GetTreeDepth(child.Children);
}
}
}
此处:
if (element.Children != null)
{
var binaryOperator = element as BinaryOperator;
这似乎是一个遗留的伪运行时-类型检查。这些一次的空检查可能会用于检查变量是否为二进制运算符,但是当添加普通的类型检查时,它们变得毫无用处。
GeneticTree
中的关注点分离/>
GeneticTree
有两件事:它为种群生成变异的个体,也就是那些个体。 br /> if (_nodes.Children != null)
{
var binaryNodeMom = _nodes as BinaryOperator;
vs.
私人布尔值_canStillSwap;
private const double MutationChance = 0.2d;
private readonly int _upperBoundary;
private readonly int _lowerBoundary;
private readonly static Random Random = new Random();
private readonly static Type[] BinaryOperations= ...;
就像当您看到在方法中组合在一起的几行代码时一样,您应该在看到大量字段时提取一个方法
另一个提示是
MutationChance
是const
,Random
和BinaryOperations
是static
,_upperBoundary
和_lowerBoundary
不是静态的,但是它们在算法运行时不会改变,它们需要传递围绕它们进行修改,只是因为无法轻易将它们设置为静态。如果您可以从域中找到更好的名称,例如GeneticTreeFactory
,则应该将第一组字段提取到MutationGenerator
。
评论
我特此提名“有史以来最好的头衔”这个问题。@RubberDuck:虽然我同意,但标题通常应该至少要多一点……有问题。阅读标题只会使您想要单击它,但不会引起对问题区域的理解。但是我喜欢...
我添加了一个答案,但是还有更多问题。我将它们留给其他审阅者指出。如果您有兴趣,可以在更合适的时间查看。