3.8 自己対局による学習 |
前節までに評価関数の実装と、評価関数を使用した中盤探索の実行を行いました。
しかし、まだ肝心の評価パラメータには全く手をつけていません。
本節では評価パラメータの調整を行う方法を説明します。
この処理は"main.c"のlearn()関数に記述されています。
この関数は長いので少しずつ見ていきましょう。
learn()の最初は以下のようになっています。
static void learn(Board *board, Evaluator *evaluator, Com *com) { char buffer[BUFFER_SIZE]; int history_color[BOARD_SIZE * BOARD_SIZE]; int i, j, move, num, turn, value; int color; int result; printf("対戦回数を入力してください\n"); get_stream(buffer, BUFFER_SIZE, stdin); num = atoi(buffer); Com_SetLevel(com, 4, 12, 12); for (i = 0; i < num; i++) {
まず最初に対戦回数を入力します。
これはコンピュータ同士で対戦を行う回数を意味しています。
learn()では、コンピュータ同士で何回も対戦を行い、対戦結果に基づいて評価パラメータの調整を行います。
入力が終わると、コンピュータの強さを設定します。
適切な調整を行うためには深い探索を行ったほうがよいのですが、それでは時間がかかってしまいます。
ここでは中盤4手読み、終盤12手読みとしました。
以降の処理は、入力した対戦回数だけ繰り返します。
ループの最初は、コンピュータ同士の対局です。
Board_Clear(board); color = BLACK; turn = 0; for (j = 0; j < 8; j++) { if (Board_CanPlay(board, color)) { move_random(board, color); history_color[turn] = color; turn++; } color = Board_OpponentColor(color); } while (1) { if (Board_CanPlay(board, color)) { if (Board_CountDisks(board, EMPTY) > 12 && get_rand(100) < 1) { move_random(board, color); } else { move = Com_NextMove(com, board, color, &value); Board_Flip(board, color, move); } history_color[turn] = color; turn++; } else if (!Board_CanPlay(board, Board_OpponentColor(color))) { break; } color = Board_OpponentColor(color); }
まず最初に盤面を初期化します。
次に、最初の8手はランダムな手を打ちます。
その後はコンピュータに思考を行わせて手を選ぶのですが、1%の確率でランダムな着手を行うようにします。
ランダムな手を打つようにすることで「これまで悪いと思っていたが実は好手」というような手を発見しやすくします。
対局が終了したら、評価値の更新を行います。
result = (Board_CountDisks(board, BLACK) - Board_CountDisks(board, WHITE)) * DISK_VALUE; for (j = Board_CountDisks(board, EMPTY); j < 8; j++) { turn--; Board_Unflip(board); } for (j = Board_CountDisks(board, EMPTY); j < BOARD_SIZE * BOARD_SIZE - 12; j++) { turn--; Board_Unflip(board); if (history_color[turn] == BLACK) { Evaluator_Update(evaluator, board, result); } else { Board_Reverse(board); Evaluator_Update(evaluator, board, -result); Board_Reverse(board); } }
最初に、対局結果を取得します。
次に、空きマスの数が8個以上になるまで手を戻します。
空きマスが少ない局面では、通常終盤探索が行われるので評価関数を使用することはありません。
評価関数を行わない局面をパラメータ調整に使っても意味がないため、
次のループ処理では、1手ずつ手を戻し、そのときの局面を使って評価パラメータの更新を行っています。
局面の評価値としては対局結果をそのまま使用しています。
白番で局面を反転させているのは、パラメータ調整に使う局面を黒番でそろえるためです。
8手目の局面まで戻したらループから抜けます。
8手目まではランダムに着手しているので、パラメータ調整のデータとしてはふさわしくないためです。
最後に評価パラメータの保存を行います。
100回対局を行う毎に評価パラメータを保存します。
if ((i + 1) % 100 == 0) { printf("学習中... %d / %d\r", i + 1 , num ); Evaluator_Save(evaluator, EVALUATOR_FILE); } } Evaluator_Save(evaluator, EVALUATOR_FILE); printf("終了しました"); }
上記の処理では終局時の石差がプラスである場合には、対局中に現れる局面も良い局面として扱い、
マイナスである場合には対局中に現れる局面も悪い局面として扱っています。
良い局面の評価値が高く、悪い局面の評価値が低くなるようにパラメータを更新することで、
コンピュータが勝ちを目指すような(もっと言うと石差を最大にするような)手を打つようになることが期待されます。
このように、報酬(ここでは終局時の石差)を最大化にするための行動を学習することを強化学習と呼びます。
本節では、強化学習のうちモンテカルロ法を使用しました。
モンテカルロ法では、最終結果だけを使用して学習を行なうという特徴があります。
ここでは強化学習の詳細には触れませんが、最終結果だけではなく例えば中盤探索の評価値を学習に使用することもできます。
興味のある方は調べてみてください。
では実際に学習を行なってみましょう。
10万局程対局を重ねればある程度強くなると思います。
筆者の環境(CPU: AthronXP 2600+)では半日程で10万局を処理できました。
学習が終了したら、対局を行なってみましょう。
強くなっているでしょうか?
次章では学習の改善も含めた性能の改善を行ないます。