您的位置:首页 > 其它

元胞自动机实现多数分类算法

2018-02-10 20:17 120 查看

元胞自动机(Cellular automaton)

元胞自动机是由元胞组成的网格,每个元胞都根据邻域的状态来选择开或关。所有的元胞都遵循同样的规则,也称为元胞的更新规则,规则根据各元胞邻域的当前状态决定元胞的下一步状态。同自然界的复杂系统一样,元胞自动机也是由大量简单个体(元胞)组成,不存在中央控制,每个个体都只与少量其他个体交互。而且元胞自动机也能表现出非常复杂的行为,它们的行为很难甚至不可能通过其更新规则来预测。元胞自动机有很多种类型,著名的“生命游戏”也是元胞自动机的一种。

初等元胞自动机(Elementary cellular automaton)

初等元胞自动机是一维两状态的元胞自动机,每个元胞仅与两个相邻元胞相连。元胞自动机的时空图表现了元胞自动机的立体构型随时间的变化,最顶上一行是一维元胞自动机的初始状态设置,下面跟着的依次是每一步更新后的状态。

执行“多数分类(Majority classification)”任务的元胞自动机

该元胞自动机要能区分初始状态中是开状态还是关状态占多数。如果是开状态占多数,最后所有元胞就应当都变成开状态。同样,如果是关状态占多数,最后所有元胞就应该都变成关状态。多数分类任务有点类似于选举,是在大家都只知道最近邻居政治观点下预测两个候选人谁会赢。

我们使用一维元胞自动机,每个元胞与相邻的6个元胞相连,这样元胞的邻域中就有7个元胞(包括自己)。一个合理的想法是:“元胞应当变成邻域中当前占多数的状态。”这就好象根据你自己和邻居的多数意见来预测哪个候选人会当选。然而,这个“局部多数投票”元胞自动机并不能完成任务。



我们使用的是梅拉妮·米歇尔(Melanie Mitchell)在《复杂》(Complexity: A Guided Tour)一书第203页给出的规则:

0000010100000110000101011000011100000111000001000001010101
0101110110010001110111000001010000000101111101111111111011
011101111111


第1位是邻域全为0时中间元胞的更新状态,第2位是邻域为0000001时中间元胞的更新状态,依次往后。由于邻域状态有 27 = 128 种可能,因此该规则有128位。但是光看这些数位是看不出这个规则如何运作,也无法知道为何它进行多数分类时适应度很高。

实现该算法的 C# 程序

下面就是相应的 C# 源程序 MainForm.cs:

using System;
using System.Drawing;
using System.Windows.Forms;

namespace Skyiv.CellularAutomaton.MajorityClassification
{
sealed class MainForm : Form
{
static readonly int sizeCellular = 3;
static readonly int nCellular = 201;
static readonly int lines = nCellular;
static readonly Pen pen = new Pen(Color.Black, sizeCellular);
static readonly string strRuler =
"0000010100000110000101011000011100000111000001000001010101" +
"0101110110010001110111000001010000000101111101111111111011" +
"011101111111";
static readonly bool[] ruler = new bool[strRuler.Length];

Graphics gc;

MainForm()
{
Text = "Majority Classification";
BackColor = Color.White;
ClientSize = new Size(nCellular * sizeCellular + 1, lines * sizeCellular + 1 + 32);
for (var i = 0; i < ruler.Length; i++) ruler[i] = strRuler[i] == '1';
}

protected override void OnPaint(PaintEventArgs e)
{
gc = e.Graphics;
DrawGrid(true);
var cellulars = GetInitCellulars();
DrawCellulars(cellulars, 0);
DisplayMessage(cellulars);
for (var i = 1; i < lines; i++)
{
StepIt(cellulars);
DrawCellulars(cellulars, i);
}
base.OnPaint(e);
}

void StepIt(bool[] cellulars)
{
var buf = new bool[cellulars.Length];
for (var i = 0; i < nCellular; i++)
buf[i] = ruler[GetValue(cellulars, i)];
Array.Copy(buf, cellulars, cellulars.Length);
}

int GetValue(bool[] cellulars, int idx)
{
var n = 0;
idx = (idx + 3) % nCellular;
for (var i = 0; i < 7; i++)
if (cellulars[(idx - i + nCellular) % nCellular])
n += 1 << i;
return n;
}

void DrawCellulars(bool[] cellulars, int line)
{
for (var i = 0; i < cellulars.Length; i++)
if (cellulars [i])
Set(i, line);
}

void DisplayMessage(bool[] cellulars)
{
var blacks = 0;
foreach (var cellular in cellulars)
if (cellular)
blacks++;
Out("Black:{0} White:{1}", blacks, cellulars.Length - blacks);
}

void Out(string fmt, params object[] args)
{
gc.DrawString(string.Format(fmt, args), new Font("Courier New", 10),
Brushes.Blue, new Point(5, lines * sizeCellular + 9));
}

bool[] GetInitCellulars()
{
var rand = new Random();
var cellulars = new bool[nCellular];
for (var i = 0; i < cellulars.Length; i++)
cellulars [i] = rand.Next() % 2 == 0;
return cellulars;
}

void DrawGrid(bool onlyBorder)
{
var pen = new Pen(Color.Red, 1);
var len = nCellular * sizeCellular;
for (var i = onlyBorder ? lines : 0; i <= lines; i++)
{
var k = i * sizeCellular;
gc.DrawLine(pen, 0, k, len, k);
}
len = lines * sizeCellular;
for (var i = onlyBorder ? nCellular : 0; i <= nCellular; i++)
{
var k = i * sizeCellular;
gc.DrawLine(pen, k, 0, k, len);
}
}

void Set(int x, int y)
{
var y2 = y * sizeCellular + sizeCellular / 2;
gc.DrawLine(pen, x * sizeCellular, y2, (x + 1) * sizeCellular, y2);
}

static void Main()
{
Application.Run(new MainForm());
}
}
}


简要分析

第9行的静态只读变量 sizeCellular 表示每个元胞方格的边长,必须为奇数。

第10行的静态只读变量 nCellular 表示每行有多少个元胞,最好为奇数,以免在多数分类时出现平局的情况。

第11行的静态只读变量 lines 表示要迭代多少次。

第13行的静态只读变量 strRuler 表示迭代的规则。

主要工作在从第29行到42行的 OnPaint 方法中进行。

第84行到第91行的 GetInitCellulars 方法随机地初始化元胞的初始状态。

第36行到第40行的循环按照指定的规则进行迭代。

第44行到第50行的 StepIt 方法执行具体的迭代步骤。

第52行到第60行的 GetValue 方法计算元胞邻域的值。

运行结果

该程序几次典型的运行结果如下所示:



上图显示白色占优,结果正确。这种情况很常见。



上图显示黑色占优,结果正确。这种情况也很常见。



上图显示白色占优,结果错误。这种情况比较少见。



上图显示黑色占优,结果错误。这个结果错误在于最终的迭代结果不是全黒,而混入了少量的白色元胞。这个程序只迭代 200 步,其实只要再迭代几步后就可以得到正确的结果。这种情况非常的少见。

参考资料

Wikipedia: Majority problem (cellular automaton)

Wikipedia: Cellular automaton

Wikipedia: Elementary cellular automaton

《复杂》,梅拉妮·米歇尔著,唐璐译,湖南科学技术出版社,2011年6月第1版
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: