| 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("終了しました");
}
では実際に学習を行なってみましょう。
といいたいところですが、次章で評価関数の高速化を行ないます。
実際に学習を行なうのはそれが終わってからにしましょう。