いものやま。

雑多な知識の寄せ集め

強化学習とニューラルネットワークを組合せてみた。(その8)

昨日は中間層のユニット数を増やす実験をしてみた。

大体うまく動いていたけど、そこで出た課題として、自己対戦だと局所的な戦略に特化してしまって、他の場面に出くわしたときにうまく動けないことが多いようだった。

そこで、複数のインスタンスを用意して、いろいろなインスタンスと対戦させるというのを試してみた。

同時複数学習

コードは以下のとおり:

#====================
# parallel_learn_test.rb
#--------------------
# 同時に複数のCOMをランダムに学習させるテスト
#====================

require_relative "mark"
require_relative "game"
require_relative "nn_sarsa_com"

@com_size = 10
@maru_filename_format = "parallel_learn_test/maru__%d.dat"
@batsu_filename_format = "parallel_learn_test/batsu_%d.dat"

def create_players
  maru_players = Array.new
  batsu_players = Array.new
  @com_size.times do
    maru_players.push NNSarsaCom.new(Mark::Maru, 128, 0.1, 0.01, 0.6)
    batsu_players.push NNSarsaCom.new(Mark::Batsu, 128, 0.1, 0.01, 0.6)
  end
  [maru_players, batsu_players]
end

def load_players
  maru_players = Array.new
  batsu_players = Array.new
  @com_size.times do |index|
    maru_players.push NNSarsaCom.load(sprintf @maru_filename_format, index)
    batsu_players.push NNSarsaCom.load(sprintf @batsu_filename_format, index)
  end
  [maru_players, batsu_players]
end

def save_players(maru_players, batsu_players)
  maru_players.each_with_index do |maru_player, index|
    maru_player.save(sprintf @maru_filename_format, index)
  end
  batsu_players.each_with_index do |batsu_player, index|
    batsu_player.save(sprintf @batsu_filename_format, index)
  end
end

def print_result(maru_players, batsu_players, time=nil)
  puts "[#{time}]" if time
  puts "----|--------------------"
  puts "#{Mark::Maru}\\#{Mark::Batsu} | 0 1 2 3 4 5 6 7 8 9"
  puts "----|--------------------"
  maru_players.each_with_index do |maru_player, maru_index|
    print sprintf "  %d |", maru_index
    batsu_players.each_with_index do |batsu_player, batsu_index|
      maru_player.learn_mode = false
      batsu_player.learn_mode = false
      game = Game.new(maru_player, batsu_player)
      result = game.start(false)
      maru_player.learn_mode = true
      batsu_player.learn_mode = true
      print " #{result}"
    end
    puts ""
  end
  puts "----|--------------------"
end

if ARGV[0] == "new"
  maru_players, batsu_players = create_players
  learn = true
elsif ARGV[0] == "continue"
  maru_players, batsu_players = load_players
  learn = true
elsif ARGV[0] == "show"
  maru_players, batsu_players = load_players
  learn = false
else
  raise 'specify "new", "continue", or "show"'
end

if learn
  1000000.times do |t|
    maru_player = maru_players.sample
    batsu_player = batsu_players.sample
    game = Game.new(maru_player, batsu_player)
    game.start(false)

    if t % 1000 == 0
      print_result(maru_players, batsu_players, t)
      save_players(maru_players, batsu_players)
    end
  end

  save_players(maru_players, batsu_players)
end

print_result(maru_players, batsu_players)

かなりやっつけのコード(^^;

引数で"new"が指定されると、○と×のインスタンスが10個ずつ作られ、ランダムに選択されたプレイヤー同士が対戦して学習を行うようになっている。
そして、引数で"continue"が指定された場合は、以前の学習から追加で学習が行われるようになっている。
また、引数で"show"が指定された場合には、学習は行わず、結果だけが表示されるようになっている。

学習結果

これを使って1,000,000回、2,000,000回、3,000,000回、4,000,000回、5,000,000回学習させた結果が、以下。

1,000,000回

----|--------------------
o\x | 0 1 2 3 4 5 6 7 8 9
----|--------------------
  0 | o . o . o . o o o o
  1 | o . o . o . o o o o
  2 | o o . o o o o o o o
  3 | o o o . o . . . . o
  4 | o . o . o . o o o o
  5 | o o . o o o o o o o
  6 | o o o . o o o . . o
  7 | o o o . o . o . . o
  8 | o o . o o o o o o x
  9 | o o . o o o o o o o
----|--------------------
○勝ち ×勝ち 引き分け
74 1 25

2,000,000回

----|--------------------
o\x | 0 1 2 3 4 5 6 7 8 9
----|--------------------
  0 | o . o . . . o . . o
  1 | o . o . . . o . . o
  2 | o o . o o o o o o .
  3 | o o x o o o x o o x
  4 | o o . o o o o o o .
  5 | o o . o o o o o o .
  6 | o o o . . . o . . o
  7 | o . o . o . o . . o
  8 | o o . o o o o o o .
  9 | o o . o o o o o o .
----|--------------------
○勝ち ×勝ち 引き分け
65 3 32

3,000,000回

----|--------------------
o\x | 0 1 2 3 4 5 6 7 8 9
----|--------------------
  0 | o . o . o o o . . o
  1 | o . o . . . o . . .
  2 | . o . o o o o o o .
  3 | . o . o o o . o o x
  4 | . o . o . o o o o .
  5 | . o . o o o o o o .
  6 | x o x o x o o o o x
  7 | o . o . . . o . . o
  8 | . o . o o o o o o .
  9 | . o . o . o . o o .
----|--------------------
○勝ち ×勝ち 引き分け
57 5 38

4,000,000回

----|--------------------
o\x | 0 1 2 3 4 5 6 7 8 9
----|--------------------
  0 | o . o . o . o . . o
  1 | o . o . o . o . . o
  2 | . o . o . o . o o .
  3 | . o . o . o . o o .
  4 | . o . o o o . o o .
  5 | . o . o . o . o o .
  6 | o . o . . . . . . o
  7 | o . o . . . o . . o
  8 | . o . o . o . o o .
  9 | . o . o o o . o o .
----|--------------------
○勝ち ×勝ち 引き分け
49 0 51

5,000,000回

----|--------------------
o\x | 0 1 2 3 4 5 6 7 8 9
----|--------------------
  0 | o . o . o . o . . o
  1 | o . o . o . o . . o
  2 | . o . o . o . o o .
  3 | . o . o . o . o o .
  4 | . o . o . o . o o .
  5 | . o . o . o . o o .
  6 | o . o . . . . . . o
  7 | o . o . . . . . . o
  8 | . o . o . o . o o .
  9 | . o . o . o . o o .
----|--------------------
○勝ち ×勝ち 引き分け
46 0 54

考察

最初は○が圧倒的に勝ち越しているけど、そこから×も盛り返して、最終的には半々くらいで引き分けになるようになっている。
ホントは全部引き分けに収束して欲しいのだけど、4,000,000回学習したときと5,000,000回学習したときがほとんど同じ結果なので、これ以上は改善されないのかもしれない。

興味深いのは、勝敗のつき方のパターンが、勝率の良くない○6, ○7を除いて、2パターン(○2, ○3, ○4, ○5, ○8, ○9のグループと、○0, ○1のグループ)になっているということ。
何度かリンクを貼っているTD Learning of Game Evaluation Functions with Hierarchical Neural Architecturesだと、HMEアーキテクチャを用いた場合、エキスパートネットワークを2つに分割したときの結果がいいみたいで、これは、入力のパターンを2つに分割して、パターンAのときにはエキスパートネットワークAを、パターンBのときにはエキスパートネットワークBを使うようにすると、結果がよかったことを意味する。
勝ち方のパターンが2つに分かれているのは、これと関係して、すなわち、入力パターンAが得意なグループと、入力パターンBが得意なグループに分かれているんじゃないかなと思う。
そして、このどちらの入力パターンも得意になるのは、振動を起こして難しいのかなと。

実際、○側の初手を確認してみると、次のようになっていた:

COM 初手
0 角(左上)
1 角(左上)
2 中央
3 中央
4 中央
5 中央
6 角(左下)
7 角(右下)
8 中央
9 中央

確かに、2パターン(中央もしくは角)に分かれている。

ちなみに、×側の様子を確認してみると、以下のようになっていた:

COM 初手中央 初手角
0 ×
1 ×
2 ×
3 ×
4
5 ×
6 ×
7 ×
8 ×
9 ×

ただし、○/△/×の意味は、以下の通り:

  • ○: 引き分けにできる
  • △: 3手目の手によっては負ける
  • ×: 負ける

見ての通り、初手中央の相手に強いものは、初手角の相手に弱く、逆に初手角の相手に強いものは、初手中央のものに弱くなっていることが分かる。
最初の目的とは違うけど、これはこれで面白い結果だと思う。

そして、戦略が散らばった(この場合2パターン)ことを考えると、複数のインスタンスを同時に学習していくのは、有効だと考えられる。
ただし、一つ一つのインスタンスの学習回数は減るので、より多くの学習が必要になる(=時間がかかる)という問題がある。
また、最終的には複数の戦略にそれぞれ特化したインスタンスが生まれる可能性が高いので、それらの結果を統合する方法が必要ということになる。

結果を統合する方法の一つとして最初考えたのは、単純に多数決をとる方法。
その戦略に強いインスタンスなら、みな同じような答えを出す可能性が高く、一方、その戦略に弱いインスタンスの答えはバラバラになるだろうから、結果的に、正しい選択が選ばれやすくなるかなと。

ただ、実際にやってみると、あまり上手くいかなかった。
これは、その戦略に弱いインスタンスがみな同じように間違えることが多かったり、あるいは、その戦略に強いインスタンスも、勝ち方が複数ある場合、意見が割れることがあったりするせい。

他の方法としては、ドロップアウトを使う手がある。
これは、ドロップアウトをすると、本質的にはそれぞれ別のインスタンスになるので、複数のインスタンスを同時に学習しているのと同じになっていて、最終的に使うときにはすべてのユニットを使うことになるので、学習の結果が統合されるのと同じ意味になるから。
これについてはまた今度考える。

また、HMEのように、階層型にして、どのインスタンスを使うべきかの学習も同時に行っていくという方法も考えられる。
ただし、この場合、学習中のインスタンスがそれぞれ個別に学習を行っていくわけではないので、1つのインスタンスで自己対戦を行ったときと同じように、特定の戦略だけ学習される可能性もあるのが難しいところ。
これについても、また今度考えていきたい。

今日はここまで!