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