4.6 評価パラメータ更新の改善 |
いままでのプログラムでは1手毎にパラメータの更新を行なっていました。
しかしこれだと特定のパターンだけ多く更新されてしまいます。
一手進めただけでは、局面のパターン構成が大きく変わらないためです。
特定のパターンだけパラメータ更新を行なうと、パラメータが安定しません。
そこで、もっとゆっくりパラメータ更新を行なうようにします。
具体的には以下のようにします。
まず"evaluator.h"の修正を行ないます。
局面を登録する関数Evaluator_Addを追加します。
同時にパラメータ更新関数Evaluator_Updateの引数を修正します。
/* 局面登録関数の追加 */ void Evaluator_Add(Evaluator *self, const Board *in_board, int in_value); /* パラメータ更新関数の修正 */ void Evaluator_Update(Evaluator *self);
関数仕様は以下の通りです。
これ以降は"evaluator.c"の修正です。
まず定数の修正を行ないます。
評価パラメータ更新の度合いUPDATOE_RATIOを0.005に上げます。
それから評価値更新に必要な出現数MIN_FREQUENCYを定義します。
MIN_FREQUENCY回以上出現していないパターンは更新を行ないません。
これによって急激な更新が行なわれることを避けます。
/* 評価パラメータ更新の度合い */ #define UPDATE_RATIO 0.005 /* パターンの最大評価値 */ #define MAX_PATTERN_VALUE (DISK_VALUE * 20) /* 評価値更新に必要な出現数 */ #define MIN_FREQUENCY 10
PatternNumは「あるパターンが何回出現したか」を格納しておく変数です。
PatternSumには評価値の差分の合計を格納します。
struct _Evaluator { int *Value[PATTERN_ID_NUM]; /* パターンの修験回数 */ int *PatternNum[PATTERN_ID_NUM]; /* 評価値差分の合計 */ double *PatternSum[PATTERN_ID_NUM]; int MirrorLine[POW_3_8]; int MirrorCorner[POW_3_8]; };
構造体の修正を行なったので、Evaluatorクラスの初期化時と終了時の処理も修正します。
初期化時にはPattern_NumとPatternSumの領域を確保します。
終了時には確保した領域を解放します。
static int Evaluator_Initialize(Evaluator *self) { int i, j; int mirror_in, mirror_out, coeff; int mirror_corner_coeff[] = { POW_3_2, POW_3_5, POW_3_0, POW_3_3, POW_3_6, POW_3_1, POW_3_4, POW_3_7 }; memset(self, 0, sizeof(Evaluator)); for (i = 0; i < PATTERN_ID_NUM; i++) { self->Value[i] = calloc(PatternSize[i], sizeof(int)); if (!self->Value[i]) { return 0; } /* 追加したメンバ変数の初期化 */ self->PatternNum[i] = calloc(PatternSize[i], sizeof(int)); if (!self->PatternNum[i]) { return 0; } self->PatternSum[i] = calloc(PatternSize[i], sizeof(double)); if (!self->PatternSum[i]) { return 0; } } (中略) return 1; } static void Evaluator_Finalize(Evaluator *self) { int i; for (i = 0; i < PATTERN_ID_NUM; i++) { /* 追加したメンバ変数の領域解放 */ if (self->PatternSum[i]) { free(self->PatternSum[i]); } if (self->PatternNum[i]) { free(self->PatternNum[i]); } if (self->Value[i]) { free(self->Value[i]); } } }
追加した局面登録関数Evaluator_Add()の実装を行ないます。
処理の内容以下の通りです。
対称なパターンが存在する場合には、対称なパターンのPatternNum、PatternSumも操作します。
Evaluator_Add()は長いので一部省略します。
static void Evaluator_AddPattern(Evaluator *self, int in_pattern, int in_id, int in_mirror, double in_diff) { self->PatternNum[in_pattern][in_id]++; self->PatternSum[in_pattern][in_id] += in_diff; if (in_mirror >= 0) { self->PatternNum[in_pattern][in_mirror] = self->PatternNum[in_pattern][in_id]; self->PatternSum[in_pattern][in_mirror] = self->PatternSum[in_pattern][in_id]; } } void Evaluator_Add(Evaluator *self, const Board *in_board, int in_value) { int index; double diff; diff = (double)(in_value - Evaluator_Value(self, in_board)); index = BOARD_INDEX_8(in_board, A4, B4, C4, D4, E4, F4, G4, H4); Evaluator_AddPattern(self, PATTERN_ID_LINE4, self->MirrorLine[index], index, diff); index = BOARD_INDEX_8(in_board, A5, B5, C5, D5, E5, F5, G5, H5); Evaluator_AddPattern(self, PATTERN_ID_LINE4, self->MirrorLine[index], index, diff); index = BOARD_INDEX_8(in_board, D1, D2, D3, D4, D5, D6, D7, D8); Evaluator_AddPattern(self, PATTERN_ID_LINE4, self->MirrorLine[index], index, diff); index = BOARD_INDEX_8(in_board, E1, E2, E3, E4, E5, E6, E7, E8); Evaluator_AddPattern(self, PATTERN_ID_LINE4, self->MirrorLine[index], index, diff); index = BOARD_INDEX_8(in_board, A3, B3, C3, D3, E3, F3, G3, H3); (中略) index = BOARD_INDEX_8(in_board, A1, B1, C1, A2, B2, C2, A3, B3); Evaluator_AddPattern(self, PATTERN_ID_CORNER8, self->MirrorCorner[index], index, diff); index = BOARD_INDEX_8(in_board, H1, G1, F1, H2, G2, F2, H3, G3); Evaluator_AddPattern(self, PATTERN_ID_CORNER8, self->MirrorCorner[index], index, diff); index = BOARD_INDEX_8(in_board, A8, B8, C8, A7, B7, C7, A6, B6); Evaluator_AddPattern(self, PATTERN_ID_CORNER8, self->MirrorCorner[index], index, diff); index = BOARD_INDEX_8(in_board, H8, G8, F8, H7, G7, F7, H6, G6); Evaluator_AddPattern(self, PATTERN_ID_CORNER8, self->MirrorCorner[index], index, diff); Evaluator_AddPattern(self, PATTERN_ID_PARITY, Board_CountDisks(in_board, EMPTY) & 1, -1, diff); }
最後にパラメータ更新関数Evaluator_Update()を修正します。
出現回数が一定以上のパターンに対して以下の式で評価値更新を行なうようにします。
(更新後の評価値)=(更新前の評価値)+(評価値差分の合計)/(出現回数)×(更新の度合い)
static void Evaluator_UpdatePattern(Evaluator *self, int in_pattern, int in_id) { int diff; if (self->PatternNum[in_pattern][in_id] > MIN_FREQUENCY) { diff = (int)(self->PatternSum[in_pattern][in_id] / self->PatternNum[in_pattern][in_id] * UPDATE_RATIO); if (MAX_PATTERN_VALUE - diff < self->Value[in_pattern][in_id]) { self->Value[in_pattern][in_id] = MAX_PATTERN_VALUE; } else if (-MAX_PATTERN_VALUE - diff > self->Value[in_pattern][in_id]) { self->Value[in_pattern][in_id] = -MAX_PATTERN_VALUE; } else { self->Value[in_pattern][in_id] += diff; } self->PatternNum[in_pattern][in_id] = 0; self->PatternSum[in_pattern][in_id] = 0; } } void Evaluator_Update(Evaluator *self) { int i, j; for (i = 0; i < PATTERN_ID_NUM; i++) { for (j = 0; j < PatternSize[i]; j++) { Evaluator_UpdatePattern(self, i, j); } } }
最後に"main.c"の学習処理learn()を修正します。
上記の修正に伴い、1手毎にEvaluator_Add()を呼び、一定対局毎にEvaluator_Update()を呼び出します。
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++) { (中略) for (j = Board_CountDisks(board, EMPTY); j < BOARD_SIZE * BOARD_SIZE - 12; j++) { turn--; Board_Unflip(board); if (history_color[turn] == BLACK) { /* 局面の登録 */ Evaluator_Add(evaluator, board, result); } else { Board_Reverse(board); /* 局面の登録 */ Evaluator_Add(evaluator, board, -result); Board_Reverse(board); } } /* パラメータ更新 */ if ((i + 1) % 10 == 0) { Evaluator_Update(evaluator); } if ((i + 1) % 100 == 0) { printf("学習中... %d / %d\r", i + 1 , num ); Evaluator_Save(evaluator, EVALUATOR_FILE); } } Evaluator_Save(evaluator, EVALUATOR_FILE); printf("終了しました"); }
では実際に学習を行なってみましょう。
といいたいところですが、次章で評価関数の高速化を行ないます。
実際に学習を行なうのはそれが終わってからにしましょう。