昨日は中間層のユニット数を増やす実験をしてみた。
大体うまく動いていたけど、そこで出た課題として、自己対戦だと局所的な戦略に特化してしまって、他の場面に出くわしたときにうまく動けないことが多いようだった。
そこで、複数のインスタンスを用意して、いろいろなインスタンスと対戦させるというのを試してみた。
同時複数学習
コードは以下のとおり:
#==================== # 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つのインスタンスで自己対戦を行ったときと同じように、特定の戦略だけ学習される可能性もあるのが難しいところ。
これについても、また今度考えていきたい。
今日はここまで!