始めての遺伝的アルゴリズム

ayumegu(プログラマー)
よろしくお願いします。

こんにちは、あゆめぐです。
マッチ箱の脳という人工知能の本をしっていますか?(10年以上前のものですが・・・。)

イラストが入っていてとてもわかりやすい人工知能の本です!
amazonのkindleだと600円で売っているのでプライム会員は0円らしい・・・。
なので気になった方はどうぞ〜
作者の方は私の好きなゲームであるがんばれ森川君2号やアストロノーカをつくったかたです。

というわけで、本日はマッチ箱の脳の最初にのっていたやつをUnityでちょっとためしてみました。
問題としては各項目3択の全10問の問題があって、それを全問正解するまでがんばらせるというものです。
詳しいことは本を・・・うん・・・。

簡単に私が覚えているルールは

  • 10体のあゆめぐちゃん(本ではマッチ箱)がテストを受けます。
  • 成績の悪い2匹には出て行ってもらいます。
  • 優秀な二匹の子供を二匹作ります
    • ランダムな位置で二匹の遺伝子(解答)を入れ替えます。
    • ランダムな確率で突然変異がおこります。
      • 私の場合は1~11のランダムで11の場合は突然変異なし
      • それ以外の場合はその番号の解答を+1しました。3になったら0にする

あとはこれを全問正解するまで繰り返させます。
問題はかわることはありません。
またそれぞれの個体が解答を途中でかえるなどもありません。 早いときで9世代、遅いと50世代くらいで全問正解しました。

とりあえず処理のソースを

あゆめぐクラス

これは1匹のあゆめぐちゃんのクラスです。
dnaにテストの解答、scoreにテストの点数が入ります

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
using UnityEngine;
using System.Collections;

public class Ayumegu {

  public int[] dna;
  public int score;

  public Ayumegu()
  {
      dna = new int[ExampleTest.QUESTION_COUNT];
      for(int i = 0; i < ExampleTest.QUESTION_COUNT; i++)
      {
          dna[i] = Random.Range(0, 3);
      }
  }

  
  public string GetDNA()
  {
      string str = "";
      for(int i = 0; i < ExampleTest.QUESTION_COUNT; i++)
      {
          str += dna[i].ToString();
      }
      return str;
  }

  public void SetDNA(string str)
  {
      dna = new int[ExampleTest.QUESTION_COUNT];
      for(int i = 0; i < ExampleTest.QUESTION_COUNT; i++)
      {
          dna[i] = int.Parse(str.Substring(i, 1));
      }
  }
}

テストクラス

こちらはテストのクラスです。
問題数や解答の変数があり、採点するメソッドGetScoreがあります。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
using UnityEngine;
using System.Collections;

public class ExampleTest{

  public const int QUESTION_COUNT = 10;     // 問題の数
  public int[] result;                      // 解答

  public ExampleTest()
  {
      // テストの解答 ランダムでもなんでも 0〜2までの数字を入れる
      result = new int[]{1, 1, 1, 1, 1, 1, 1, 1, 1, 1};
  }

  public int GetScore(int[] answerList)
  {
      int score = 0;
      for(int i = 0; i < QUESTION_COUNT; i++)
      {
          if(result[i] == answerList[i])
          {
              score += 1;
          }
      }
      return score;
  }

}

動かすクラス

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class GATest : MonoBehaviour {

  public const int AYUMEGU_COUNT = 10;
  public List<Ayumegu> ayumeguList;
  public ExampleTest test;
  public int count = 0;

  void Start () {
      test = new ExampleTest();
      ayumeguList = new List<Ayumegu>();
      for(int i = 0; i < AYUMEGU_COUNT; i++)
      {
          ayumeguList.Add (new Ayumegu());
      }

      Test();
  }

  // テストを受けます
  public void Test()
  {
      for(int i = 0; i < AYUMEGU_COUNT; i++)
      {
          ayumeguList[i].score = test.GetScore(ayumeguList[i].dna);
      }
      Next();
  }

  // 結果をもとに点数の悪い子には出て行ってもらいます
  public void Next()
  {
      ayumeguList.Sort(delegate(Ayumegu dna1, Ayumegu dna2){ return dna1.score - dna2.score; });

      // 全問正解していたら終了
      if(ayumeguList[ayumeguList.Count - 1].score == ExampleTest.QUESTION_COUNT)
      {        
          Debug.Log (count + "End" + ayumeguList[ayumeguList.Count - 1].GetDNA());
      }
      else
      {
          count++;
          for(int i = 0; i < 2; i++)
          {
              ayumeguList.RemoveAt(i);
          }

          CreateChild(ayumeguList[ayumeguList.Count - 1], ayumeguList[ayumeguList.Count - 2]);
      }
  }

  // 優秀な二匹の遺伝子を引き継いだ子を2匹追加します。
  public void CreateChild(Ayumegu parent1, Ayumegu parent2)
  {
      int rand = Random.Range(1, ExampleTest.QUESTION_COUNT - 1);

      string parent1Dna = parent1.GetDNA();
      string parent2Dna = parent2.GetDNA();

      string child1Dna = parent1Dna.Substring(0, rand) + parent2Dna.Substring(rand, ExampleTest.QUESTION_COUNT - rand);
      string child2Dna = parent2Dna.Substring(0, rand) + parent1Dna.Substring(rand, ExampleTest.QUESTION_COUNT - rand);


      child1Dna = ChangeDna(child1Dna);
      child2Dna = ChangeDna(child2Dna);

      Ayumegu child1 = new Ayumegu();
      child1.SetDNA(child1Dna);
      ayumeguList.Add (child1);

      Ayumegu child2 = new Ayumegu();
      child2.SetDNA(child2Dna);
      ayumeguList.Add (child2);
      Test();
  }

  // 遺伝子の一部を突然変異させます
  private string ChangeDna(string dna)
  {
      int changePoint = Random.Range(1, 12);
      if(changePoint < 11)
      {
          int no = int.Parse(dna.Substring(changePoint - 1, 1));
          no++;
          if(no > 2) no = 0;
          dna = dna.Substring(0, changePoint - 1) + no.ToString() + dna.Substring(changePoint, ExampleTest.QUESTION_COUNT - changePoint);
      }
      return dna;
  }
}

えーっとえーっとコンソールはってもつまらないと思うので次の記事でUIでもつけてみようかな・・・。
うん、これだけだとシュールだし、なにしてるかわからないよね・・・。
では今回はここまでで・・・w