TadaoYamaokaの開発日記

個人開発しているスマホアプリや将棋AIの開発ネタを中心に書いていきます。

【書評」ディープラーニング 学習する機械 ヤン・ルカン、人工知能を語る

このブログは無味乾燥な実験結果ばかり書いているが、たまには書評も書いてみる。

最近読み終わった本で、「ディープラーニング 学習する機械 ヤン・ルカン、人工知能を語る」がとても面白かったので紹介する。

実は人文書

タイトルだけみると理系の工学系の専門書のようだが、はじめ自分もそのようなつもり読み始めたが、読んでみたらむしろ人文書に分類されるような内容だった。
人の知能とは何かを、最先端の人工知能の研究者の視点から書かれている。
機械学習の数学的な説明も少しあるがその部分は読み飛ばしても問題がなく、筆者の人工知能の研究に携わってきた際のエピソードが面白く、また知能とはなにかという哲学的なテーマを筆者の視点から語っている。
どちらの内容も読みごたえがあった。

私と人文系

私は、大学時代は工学部だったが人文系の講義もいくつか選択して、たまたま受けた橋爪大三郎先生の講義で人文系の科目にも興味を持って、一時期「方法序説」「純粋理性批判」「道徳の系譜」「論理哲学論考」「存在と時間」といった西洋哲学の本を読んでいた。
これらは唯心論的な立場で、人間の知能は特別な存在として、観測される現象は、あくまで"人によって"認識される現象であるという立場で語られており、人の認識について理解を深めることができたが、それでも工学系の自分にとっては違和感を覚えるものであった。

著者の主張

この本で筆者は、知能を唯物論的な立場でとらえて、脳は「計算」する機械であり、その計算は原理的にはコンピュータで再現でき、意識とは「大規模な神経回路網の創発特性にすぎない」と確信している。
最近の研究でわかってきたことや、ニューラルネットワークが一部を再現していることから、知能は神秘ではなく原理が明らかできるものと、読者に語りかける。
人工知能の研究とは唯心論へのいわばアンチテーゼなのである。
ただし、これは筆者の信じるところであり、現在わかっているのがどの程度で、まだ脳の大部分はわかっておらず、それらを解明する道筋すら立っていないことも謙虚に語っている。


ということで、タイトルとは違って非エンジニアの方でも読みごたえのある内容になっており、哲学書として読んでみても面白いのではないかと思って紹介してみた。

正月早々長文にお付き合いありがとうございました(๑•ᴗ•๑)

千日手を予測する

昨年11月に行われた第2回電竜戦では、A級リーグでは先手勝率が70%と高かったことがコンピュータ将棋関係者の間で話題になった。
また、戦型は相掛かりが多かった。
角換わりは後手が千日手を狙いやすく、有利な先手で後手に千日手にされないように定跡で角換わりを避けるチームが多かったためである。
dlshogiも、先手で定跡を用いない場合、角換わりを選択してしまうため、定跡作成時に序盤数手以内で角換わりになる手を2番目の候補と入れ替えて、角換わりを避けるようにした。
コンピュータ将棋では千日手が戦略において重要になりつつある。

現状の先手の千日手の回避方法と問題点

現在、dlshogiでは探索中に千日手になる場合は、千日手になった局面の価値をUSIオプションのDraw_Value_BlackとDraw_Value_Whiteで設定した値にしている。
Draw_Value_Blackは、先手の千日手の価値で、これを0.5より小さくし、後手の千日手の価値のDraw_Value_Whiteを0.5より大きくすることで、先手が千日手を選ぶことを回避している。

しかし、角換わりでは、80手を超えて千日手になる場合があり、序盤の探索では、上記の千日手の価値が探索結果に反映されない。

千日手を予測する

DL系チェスAIのLeela Chess Zeroでも、WDLヘッドと呼ばれる方法で、勝ち、引き分け、負けを別々に予測して、探索時に利用することを行っている。
コンピュータチェスでは引き分けが多く、引き分けが戦略において重要なため、2019年の段階で取り入れられている。

コンピュータ将棋でも千日手が重要になりつつあるため、ディープラーニングのモデルで、千日手を予測するようにして、その予測を探索に活かすことを考える。

Leela Chess Zeroでは、勝ちと負けも別々の出力にしているが、将棋では千日手の割合が勝ち、負けになる場合より低いため、勝ち、千日手、負けを合計1とする確率とすると、データの偏りの影響が大きくなる。
そこで、千日手のみを別の出力にして、勝ち、負けは従来通り2値分類とする。

千日手の予測を利用した探索方法

局面の千日手の予測値を[0, 1]の確率として出力し、勝敗の確率と同様にノードにバックアップする。

UCBを計算する際に、千日手の予測値に合わせて、価値の中心を0.5から、USIオプションDraw_Value_BlackとDraw_Value_Whiteに応じてずらすように線形に変換する。
こうすることで、先手の千日手の予測値が高い手の価値を、0.5から下げることができる。
同じ価値が0.5の手でも千日手の予測が低い場合は、そのまま値は0.5になる。

従来は、同じ価値が0.5と予測していた手でも、千日手になりやすさによって、差を付けることができる。

実験

千日手を予想できるか実験を行った。

ニューラルネットワークの変更

現在のdlshogiのResNet15ブロックのモデルに、千日手を2値分類で出力するヘッドを追加した。

教師データ

序盤の価値を学習したいため、初期局面集にはfloodgateの8手目までの頻出局面を使用し、そこから36手までランダム性あり(最善手から勝率差2.5%までを訪問回数に温度を適用した確率分布で選択)で自己対局を行い、1万プレイアウトで1億局面を生成した。

学習

バッチサイズ4096、学習率0.04、平均化あり、評価値補正ありで1エポック学習した結果は以下の通り。

2022/01/03 18:06:05     INFO    epoch = 1, steps = 16797, train loss avr = 1.8192283, 0.5921775, 0.5903943, 0.1420839, 2.5528958, test loss = 1.7488920, 0.5522317, 0.6426893, 0.0382176, 2.3694636, test accuracy = 0.4484011, 0.7039824, 0.9992594, test entropy = 1.6746912, 0.5955356

train loss avrと、test lossのカンマ区切りの3番目の値が千日手の損失、test accuracyの3番目の値が千日手の正解率である。

千日手の正解率は、ほとんどが千日手ではないため、1.0に近くなる。
データの比率に応じて重み付けする方がよいかもしれないが、一旦重みなしで学習した。

MCTSバックアップの結果

UCBの計算は従来のままで、300万プレイアウトを行い、千日手の予測をノードにバックアップした際、ルートノードの各子ノードの千日手の確率を確認した。
dlshogiのDebugMessageを有効にして各手の情報を出力した。

  • 平手開始局面
0:1g1f move_count:11735 nnrate:0.0611579 win_rate:0.495021 draw:0.0668833
1:2g2f move_count:2337361 nnrate:0.287791 win_rate:0.547377 draw:0.0605795
2:3g3f move_count:541 nnrate:0.00382443 win_rate:0.476003 draw:0.0561522
3:4g4f move_count:287 nnrate:0.00191426 win_rate:0.479923 draw:0.0649844
4:5g5f move_count:327 nnrate:0.00257173 win_rate:0.467597 draw:0.0595138
5:6g6f move_count:987 nnrate:0.007876 win_rate:0.46671 draw:0.0766365
6:7g7f move_count:538516 nnrate:0.324734 win_rate:0.542494 draw:0.0603726
7:8g8f move_count:65 nnrate:0.00160816 win_rate:0.29254 draw:0.0354054
8:9g9f move_count:14821 nnrate:0.0561628 win_rate:0.509682 draw:0.064395
9:1i1h move_count:5 nnrate:7.3459e-05 win_rate:0.41165 draw:0.0580017
10:9i9h move_count:10 nnrate:9.67965e-05 win_rate:0.437868 draw:0.0565541
11:3i3h move_count:8370 nnrate:0.0216606 win_rate:0.521986 draw:0.0585203
12:3i4h move_count:8654 nnrate:0.0308613 win_rate:0.511935 draw:0.0658712
13:7i6h move_count:6128 nnrate:0.0318323 win_rate:0.495197 draw:0.0689616
14:7i7h move_count:1807 nnrate:0.0188768 win_rate:0.441253 draw:0.0548807
15:2h1h move_count:81 nnrate:0.00134084 win_rate:0.378411 draw:0.0542994
16:2h3h move_count:182 nnrate:0.00286659 win_rate:0.387395 draw:0.0504418
17:2h4h move_count:100 nnrate:0.00153019 win_rate:0.391744 draw:0.0553084
18:2h5h move_count:154 nnrate:0.00280101 win_rate:0.362751 draw:0.0516878
19:2h6h move_count:990 nnrate:0.0179083 win_rate:0.362621 draw:0.0516561
20:2h7h move_count:850 nnrate:0.0142273 win_rate:0.376696 draw:0.0535907
21:4i3h move_count:1 nnrate:1.83503e-05 win_rate:0.432962 draw:0.0596469
22:4i4h move_count:9 nnrate:6.20836e-05 win_rate:0.478401 draw:0.061416
23:4i5h move_count:819 nnrate:0.00338495 win_rate:0.506092 draw:0.0677877
24:6i5h move_count:15 nnrate:0.000211752 win_rate:0.410581 draw:0.0539416
25:6i6h move_count:39 nnrate:0.000263822 win_rate:0.480577 draw:0.0599286
26:6i7h move_count:65700 nnrate:0.0991663 win_rate:0.533101 draw:0.0671388
27:5i4h move_count:23 nnrate:0.000307105 win_rate:0.415958 draw:0.0624212
28:5i5h move_count:90 nnrate:0.00054318 win_rate:0.487062 draw:0.0569803
29:5i6h move_count:1401 nnrate:0.00432681 win_rate:0.516858 draw:0.0522245

draw:の後の数値がバックアップされた千日手の確率である。
2g2f (二六歩)と、7g7f (7六歩)の訪問回数(move_count)が多く、千日手の確率は、それぞれ0.0605795、0.0603726になっている。
2g2f (二六歩)の方が、わずかに千日手の確率が高いが、ほぼ同じである。
平手開始局面では、千日手の予測が妥当かどうか判断できない。

  • 先手が角換わりを選択できる局面
position startpos moves 2g2f 8c8d 7g7f 4a3b 2f2e 8d8e

f:id:TadaoYamaoka:20220103210853p:plain
|

先手が、7七角を指せば角換わりになる局面である。
この局面の各手の千日手の確率は以下の通り。

0:1g1f move_count:2921 nnrate:0.0149358 win_rate:0.484744 draw:0.0418013
1:2e2d move_count:52186 nnrate:0.0935355 win_rate:0.519303 draw:0.0472217
2:3g3f move_count:65 nnrate:0.000483536 win_rate:0.461186 draw:0.030724
3:4g4f move_count:11 nnrate:7.75115e-05 win_rate:0.469704 draw:0.0343168
4:5g5f move_count:12 nnrate:0.000155746 win_rate:0.40977 draw:0.0342723
5:6g6f move_count:54 nnrate:0.00044754 win_rate:0.452436 draw:0.0493007
6:7f7e move_count:187 nnrate:0.00260336 win_rate:0.393394 draw:0.0356545
7:8g8f move_count:7 nnrate:0.000263042 win_rate:0.179321 draw:0.0152975
8:9g9f move_count:6224 nnrate:0.029359 win_rate:0.488845 draw:0.0395514
9:1i1h move_count:5 nnrate:6.23639e-05 win_rate:0.416613 draw:0.0355014
10:9i9h move_count:14 nnrate:0.000117823 win_rate:0.446026 draw:0.035784
11:8i7g move_count:88 nnrate:0.0010974 win_rate:0.409444 draw:0.0314612
12:3i3h move_count:8498 nnrate:0.0226585 win_rate:0.510202 draw:0.0393935
13:3i4h move_count:955 nnrate:0.00526842 win_rate:0.480586 draw:0.0402083
14:7i6h move_count:1125 nnrate:0.0103155 win_rate:0.442541 draw:0.0411338
15:7i7h move_count:6359 nnrate:0.0299131 win_rate:0.488991 draw:0.0421639
16:8h3c+ move_count:20 nnrate:0.000875257 win_rate:0.101496 draw:0.0101548
17:8h4d move_count:11 nnrate:0.000294209 win_rate:0.270423 draw:0.0333792
18:8h5e move_count:59 nnrate:0.000736953 win_rate:0.406358 draw:0.0420949
19:8h6f move_count:432 nnrate:0.0028955 win_rate:0.46818 draw:0.0415992
20:8h7g move_count:2844361 nnrate:0.514368 win_rate:0.536125 draw:0.079909
21:2h1h move_count:3 nnrate:7.4672e-05 win_rate:0.330184 draw:0.0343858
22:2h2f move_count:528 nnrate:0.0036502 win_rate:0.465786 draw:0.0422589
23:2h2g move_count:8 nnrate:6.99477e-05 win_rate:0.45394 draw:0.0345064
24:2h3h move_count:1 nnrate:2.32848e-05 win_rate:0.281208 draw:0.0316188
25:2h4h move_count:1 nnrate:6.51619e-06 win_rate:0.270866 draw:0.0322223
26:2h5h move_count:2 nnrate:6.07541e-05 win_rate:0.309892 draw:0.0335276
27:2h6h move_count:15 nnrate:0.00035687 win_rate:0.296944 draw:0.0279614
28:2h7h move_count:12 nnrate:0.000279252 win_rate:0.306955 draw:0.0295265
29:4i3h move_count:3 nnrate:2.59912e-05 win_rate:0.452154 draw:0.0369317
30:4i4h move_count:12 nnrate:7.84216e-05 win_rate:0.474661 draw:0.0381489
31:4i5h move_count:223 nnrate:0.00124876 win_rate:0.479916 draw:0.0421533
32:6i5h move_count:2 nnrate:2.07389e-05 win_rate:0.437223 draw:0.0396998
33:6i6h move_count:75 nnrate:0.000477541 win_rate:0.47053 draw:0.039563
34:6i7h move_count:119816 nnrate:0.230684 win_rate:0.517926 draw:0.0460936
35:5i4h move_count:141 nnrate:0.000790783 win_rate:0.479209 draw:0.04173
36:5i5h move_count:8859 nnrate:0.0241661 win_rate:0.509562 draw:0.0415073
37:5i6h move_count:3423 nnrate:0.00752173 win_rate:0.515073 draw:0.0401955

8h7g(7七角)、6i7h(7八金)の順に訪問回数が大きい。
それぞれの千日手の確率は、0.079909、0.0460936となっており、8h7g(7七角)の方が千日手になる確率が高いと予測している。
8h7g(7七角)は角換わりになる可能性が高いため、妥当な予測ができていそうである。

探索時のUCBの計算で千日手の予測を考慮することで、千日手を避けた探索ができそうである。

まとめ

ディープラーニングのモデルで千日手を予測し、探索時に利用することで、序盤から千日手を避ける手を選択できそうということがわかった。
次回は、実際に探索時に千日手の予測を利用した探索の検証を行う予定。

入力特徴量作成の速度改善

年末にDiscordのやりとりで、dlshogiの入力特徴量作成で、各マスの利きを求めるために、各マス×手番ごとにattackersToを呼び出すのは無駄と、やねうら氏からご指摘をいただいた。
その通りなので、改善を行った。

改善したコード

各マスの利きをattackersToで調べると、処理の重い飛車・角の飛び利きを81マス×2(手番)回呼び出すことになる。
盤上の駒ごとに利きを調べて、各駒が利いているマスごとに処理することで、飛び利きの呼び出し回数を盤上にある駒の数に減らすことができる。

改善前のコード
	const Bitboard occupied_bb = position.occupiedBB();

	// 駒の利き(駒種でマージ)
	Bitboard attacks[ColorNum][PieceTypeNum] = {
		{ { 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 } },
		{ { 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 },{ 0, 0 } },
	};
	for (Square sq = SQ11; sq < SquareNum; sq++) {
		const Piece p = position.piece(sq);
		if (p != Empty) {
			const Color pc = pieceToColor(p);
			const PieceType pt = pieceToPieceType(p);
			const Bitboard bb = position.attacksFrom(pt, pc, sq, occupied_bb);
			attacks[pc][pt] |= bb;
		}
	}

	for (Color c = Black; c < ColorNum; ++c) {
		// 白の場合、色を反転
		const Color c2 = turn == Black ? c : oppositeColor(c);

		// 駒の配置
		Bitboard bb[PieceTypeNum];
		for (PieceType pt = Pawn; pt < PieceTypeNum; ++pt) {
			bb[pt] = position.bbOf(pt, c);
		}

		for (Square sq = SQ11; sq < SquareNum; ++sq) {
			// 白の場合、盤面を180度回転
			const Square sq2 = turn == Black ? sq : SQ99 - sq;

			for (PieceType pt = Pawn; pt < PieceTypeNum; ++pt) {
				// 駒の配置
				if (bb[pt].isSet(sq)) {
					(*features1)[c2][pt - 1][sq2] = 1;
				}

				// 駒の利き
				if (attacks[c][pt].isSet(sq)) {
					(*features1)[c2][PIECETYPE_NUM + pt - 1][sq2] = 1;
				}
			}

			// 利き数
			const int num = std::min(MAX_ATTACK_NUM, position.attackersTo(c, sq, occupied_bb).popCount());
			for (int k = 0; k < num; k++) {
				(*features1)[c2][PIECETYPE_NUM + PIECETYPE_NUM + k][sq2] = 1;
			}
		}
	}
改善後のコード
	const Bitboard occupied_bb = position.occupiedBB();

	// 歩と歩以外に分ける
	Bitboard pawns_bb = position.bbOf(Pawn);
	Bitboard without_pawns_bb = occupied_bb & ~pawns_bb;
	// 利き数集計用
	int attack_num[ColorNum][SquareNum] = {};

	// 歩以外
	FOREACH_BB(without_pawns_bb, Square sq, {
		const Piece pc = position.piece(sq);
		const PieceType pt = pieceToPieceType(pc);
		Color c = pieceToColor(pc);
		Bitboard attacks = Position::attacksFrom(pt, c, sq, occupied_bb);

		// 後手の場合、色を反転し、盤面を180度回転
		if (turn == White) {
			c = oppositeColor(c);
			sq = SQ99 - sq;
		}

		// 駒の配置
		(*features1)[c][pt - 1][sq] = 1.0f;

		FOREACH_BB(attacks, Square to, {
			// 後手の場合、盤面を180度回転
			if (turn == White) to = SQ99 - to;

			// 駒の利き
			(*features1)[c][PIECETYPE_NUM + pt - 1][to] = 1.0f;

			// 利き数
			auto& num = attack_num[c][to];
			if (num < MAX_ATTACK_NUM) {
				(*features1)[c][PIECETYPE_NUM + PIECETYPE_NUM + num][to] = 1.0f;
				num++;
			}
		});
	});

	for (Color c = Black; c < ColorNum; ++c) {
		// 後手の場合、色を反転
		const Color c2 = turn == Black ? c : oppositeColor(c);

		// 歩
		Bitboard pawns_bb2 = pawns_bb & position.bbOf(c2);
		const SquareDelta pawnDelta = c == Black ? DeltaN : DeltaS;
		FOREACH_BB(pawns_bb2, Square sq, {
			// 後手の場合、盤面を180度回転
			if (turn == White) sq = SQ99 - sq;

			// 駒の配置
			(*features1)[c][Pawn - 1][sq] = 1.0f;

			// 駒の利き
			const Square to = sq + pawnDelta; // 1マス先
			(*features1)[c][PIECETYPE_NUM + Pawn - 1][to] = 1.0f;

			// 利き数
			auto& num = attack_num[c][to];
			if (num < MAX_ATTACK_NUM) {
				(*features1)[c][PIECETYPE_NUM + PIECETYPE_NUM + num][to] = 1.0f;
				num++;
			}
		});
	}

速度比較

floodgateのR3500同士の棋譜から抽出した6997258局面を使用して、入力特徴量作成処理にかかった時間の合計を測定した。
20回測定した結果は以下の通り。モデルサイズは15ブロック。

改善前(ms) 改善後(ms) 改善後/改善前
平均 39533 5384 13.6%
中央値 39422 5378 13.6%
最大値 40597 5430 13.4%
最小値 39287 5363 13.7%

平均で、処理時間が13.6%になった。

NPS比較

floodgateの棋譜からサンプリングした100局面で、1秒探索した際のNPSを測定した。
10回測定した平均値を算出し、100局面について平均、中央値、最大値、最小値を算出した。

RTX3090、2スレッド
改善前(ms) 改善後(ms) 改善後/改善前
平均値 28862 29246 101.5%
中央値 30155 30196 100.1%
最大値 31660 31557 106.4%
最小値 17078 17746 99.1%

※改善後/改善前は同一局面での比

100局面の比の平均で1.5%NPSが向上した。

A100x8、GPUあたり4スレッド
改善前(ms) 改善後(ms) 改善後/改善前
平均値 295397 292695 99.1%
中央値 297469 294976 99.2%
最大値 343213 343739 102.3%
最小値 233813 229470 94.7%

※改善後/改善前は同一局面での比

100局面の比の平均で0.9%NPSが低下した。

入力特徴量作成処理の時間が短くなっているのに、NPSが低下する理由がわからない。
NPSはGPUの温度などで毎回ぶれるため、測定誤差と考えられる。
入力特徴量作成は、各プレイアウトで1回しか呼び出さないため、ボトルネックもなっていないため、NPSには影響がないと考えられる。

まとめ

入力特徴量作成処理に無駄な処理があっため、改善を行ったことで、処理時間が13.6%になった。
ただし、入力特徴量作成処理はボトルネックになっておらず、NPSにはほとんど影響がなかった。

学習時にも、入力特徴量作成処理を呼び出しているが、データローダの並列化を行っているため、影響はなさそうである。

追記

10ブロックのモデルでも確認した。

RTX3090、2スレッド
改善前(ms) 改善後(ms) 改善後/改善前
平均値 43589 46531 107.1%
中央値 46058 51137 108.0%
最大値 61426 57525 113.3%
最小値 19727 20366 90.7%

※改善後/改善前は同一局面での比

100局面の比の平均で7.1%NPSが向上した。

A100x8、GPUあたり5スレッド
改善前(ms) 改善後(ms) 改善後/改善前
平均値 306643 315190 102.8%
中央値 306638 313558 102.6%
最大値 419507 449810 107.2%
最小値 247300 243651 97.5%

※改善後/改善前は同一局面での比

100局面の比の平均で2.8%NPSが向上した。

2021/1/2 追記

改善後(ms)の数値が20回の平均値ではなく10回目の数値になっていたため修正した。

書籍のpython-dlshogi2のバグについて 続き

先日の記事で報告した、書籍「強い将棋ソフトの創りかた」の第5章のソースコードのmake_move_labelの移動方向の判定に誤りがあった点について、GitHubソースコードを修正して、再学習したモデルファイルをプッシュしました。

make_move_labelで移動方向の判定が誤っている · Issue #5 · TadaoYamaoka/python-dlshogi2 · GitHub

checkpoints/checkpoint.pthについて

python-dlshogi2のGitHubリポジトリにあるcheckpoints/checkpoint.pthは、書籍の第7章のデータをすべて使用して、3epoch学習したチェックポイントです。

Colabではなくローカルで学習しています。
参考までに、学習に使用したシェルスクリプトは以下の通りです。Colabの場合は分割して学習しないと、データを読み込むためのメモリが不足します。

#!/bin/sh
python -m pydlshogi2.train floodgate_2019-2021_r3500-*.hcpe suisho3kai-*.hcpe dlshogi_with_gct-*.hcpe floodgate_test_2017-2018_r3500_eval5000.hcpe -b 4096 --lr 0.04 --eval_interval 1000 --log log_train_pydlshogi2-001.txt
python -m pydlshogi2.train floodgate_2019-2021_r3500-*.hcpe suisho3kai-*.hcpe dlshogi_with_gct-*.hcpe floodgate_test_2017-2018_r3500_eval5000.hcpe -b 4096 --lr 0.004 --eval_interval 1000 -r checkpoints/checkpoint-001.pth --log log_train_pydlshogi2-002.txt
python -m pydlshogi2.train floodgate_2019-2021_r3500-*.hcpe suisho3kai-*.hcpe dlshogi_with_gct-*.hcpe floodgate_test_2017-2018_r3500_eval5000.hcpe -b 4096 --lr 0.0004 --eval_interval 1000 -r checkpoints/checkpoint-002.pth --log log_train_pydlshogi2-003.txt

GPUがV100で、1epochに約18時間かかります。

48先生からプルリクを頂いているAMP対応を入れると、時間は半分くらいになると思います。
AMP対応 by bleu48 · Pull Request #4 · TadaoYamaoka/python-dlshogi2 · GitHub

3epoch学習した際のテスト精度は以下の通りです。

2021/12/28 15:15:11     INFO    epoch = 3, steps = 195555, train loss avr = 1.6887461, 0.4733937, 2.1621399, test loss = 1.6887461, 0.4733937, 2.1621399, test accuracy = 0.4764219, 0.7450078

floodgateに放流

checkpoint.pthを使用したpython-dlshogi2を、しばらくfloodgateに放流させておきます。
http://wdoor.c.u-tokyo.ac.jp/shogi/view/show-player.cgi?event=LATEST&filter=floodgate&show_self_play=1&user=python-dlshogi2
技巧2 1コアには勝てているようです。

まとめ

書籍の第5章のmake_move_labelの移動方向の判定にバグがあったため修正し、再学習したモデルをGitHubにプッシュしました。
Colabのノートブックについては、特にpython-dlshogi2のバージョン指定はしていないので、これから実行する場合は修正後のコードが反映されます。

すでにローカルでpython-dlshogi2を試されていた場合は、申し訳ありませんがgit pullを行ってください。
また、修正前のコードで学習したモデルと互換性がないため、再学習が必要になりますm(__)m。

書籍のpython-dlshogi2のバグについて

書籍「強い将棋ソフトの創りかた」の第5章のpython-dlshogi2に精度に影響のあるバグがあったので報告します。

誤り箇所

p.109 リスト5.2のfeatures.pyのmake_move_labelの移動方向を判定する処理は、以下のように定義されています。
python-dlshogi2/features.py at 4a1b227d8dfdd32cc2a77f151341dacfc904776b · TadaoYamaoka/python-dlshogi2 · GitHub

        # 移動方向
        to_y, to_x = divmod(to_sq, 9)
        from_y, from_x = divmod(from_sq, 9)
        dir_x = to_x - from_x
        dir_y = to_y - from_y
        if dir_y < 0:
            if dir_x == 0:
                move_direction = UP
            elif dir_x < 0:
                move_direction = UP_LEFT
            else:  # dir_x > 0
                move_direction = UP_RIGHT
        elif dir_y == 0:
            if dir_x < 0:
                move_direction = LEFT
            else:  # dir_x > 0
                move_direction = RIGHT
        elif dir_y > 0:
            if dir_x == 0:
                move_direction = DOWN
            elif dir_x < 0:
                move_direction = DOWN_LEFT
            else:  # dir_x > 0
                move_direction = DOWN_RIGHT
        else:  # dir_y == -2
            if dir_x == -1:
                move_direction = UP2_LEFT
            else:  # dir_x == 1
                move_direction = UP2_RIGHT

正しい内容

座標系の扱いの誤り

to_sqと、from_sqは、図5.13で説明しているように1一を0、1九を8、9九を80とする縦方向に採番した数値になっているため、divmodの結果を格納している変数のto_xとto_ yおよびfrom_xとfrom_yは、それぞれ逆になり、

        to_x, to_y = divmod(to_sq, 9)
        from_x, from_y = divmod(from_sq, 9)

とするのが正しいです。
前著は将棋ライブラリにpython-shogiを使用していたため、そのコードを流用していたため誤っていました。

LEFTとRIGHTが逆

x < 0のときにLEFTとしていましたが、将棋盤の右上から採番しているため、RIGHTが正しいです。

桂馬の動きが判定されない

最後のelseは、上にy < 0があるため通らない処理になっていました。
また、x, yが逆になっているため、elseの問題がなくても、桂馬の動きが判定されてないようになっていました。

修正後コード

以上を修正した後のコードは以下のようになります。

make_move_labelの移動方向を判定する処理
        # 移動方向
        to_x, to_y = divmod(to_sq, 9)
        from_x, from_y = divmod(from_sq, 9)
        dir_x = to_x - from_x
        dir_y = to_y - from_y
        if dir_y < 0:
            if dir_x == 0:
                move_direction = UP
            elif dir_y == -2 and dir_x == -1:
                move_direction = UP2_RIGHT
            elif dir_y == -2 and dir_x == 1:
                move_direction = UP2_LEFT
            elif dir_x < 0:
                move_direction = UP_RIGHT
            else:  # dir_x > 0
                move_direction = UP_LEFT
        elif dir_y == 0:
            if dir_x < 0:
                move_direction = RIGHT
            else:  # dir_x > 0
                move_direction = LEFT
        else:  # dir_y > 0
            if dir_x == 0:
                move_direction = DOWN
            elif dir_x < 0:
                move_direction = DOWN_RIGHT
            else:  # dir_x > 0
                move_direction = DOWN_LEFT

なお、この修正で、出力ラベルはdlshogiライブラリと完全一致するようになります。

学習済みチェックポイントへの影響

修正を行うと、以前のコードで学習したチェックポイントと互換性がなくなります。
一から再学習が必要です。
桂馬の動きが正しく学習できるようになるため、学習し直すと以前よりも強くなるはずです。

正誤表

非公式の正誤表に掲載しました。
正誤表 · TadaoYamaoka/ShogiAIBook2 Wiki · GitHub

出版社の公式のサポートページには後日掲載してもらいます。

GitHubのコードとチェックポイントの修正

チェックポイントを再学習してから、コードとチェックポイントの更新を行う予定です。
第7章のデータを使ったチェックポイント(checkpoint.pth)の再学習に時間がかかるため、明日になる予定です。
更新が完了したら、この記事に追記します。

お詫び

書籍を購入して、すでにpython-dlshogi2でモデルの学習を試された方は、申し訳ありません。

なお、この修正は、第7章、8章のdlshogiライブラリを使用した学習には影響はありません。
影響範囲は、第5章のpython-dlshogi2で学習したチェックポイントのみになります。

Qugiyのビット演算を試す その3

先日、飛車と角の利きのビット演算を実装し、NPSが平均1%程度向上することが確認できたので、今回は強さを測定した。

強さの測定

dlshogi互角局面集を使用して、GPU A100x2、GPUごと3スレッド、水匠5は30スレッドで、測定した。

# PLAYER               :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
1 qugiy_rook_bishop    :    43.0   20.5   228.5     388    59      91  192   73  123    19
2 master               :    18.4   21.0   211.5     393    54     100  174   75  144    19
3 suisho5-30th         :   -61.4   20.7   144.0     387    37     ---  105   78  204    20

対局集が十分ではないが、R+24.6になった。

まとめ

Qugiyの飛車と角の利きのビット演算を実装することで、強くなることが確認できた。
強くなることが確認できたので、masterブランチにマージした。
GitHub - TadaoYamaoka/DeepLearningShogi

ただし、CPUがZEN2以外ではNPSは変わらないため、特に更新する必要はない。

【書籍】強い将棋ソフトの創りかた

先月、このブログでお知らせしていた書籍「強い将棋ソフトの創りかた」が12/20に発売されました。

book.mynavi.jp


応援Tweetしてもらいました。

本を買わずにデータだけダウンロードしたら😡です。

書籍の内容(再掲)

ディープラーニング系将棋AIのアルゴリズムの解説と、ディープラーニング系将棋AIの仕組みを実装して理解するためpythonで将棋AIを実装するという内容と、dlshogiライブラリを使用してGCT電竜を超える将棋AIモデルを学習する方法を解説しています。

Pythonで実装する将棋AIは、時間制御やPonderという大会にでるソフトが備えている機能も実装した本格的なものになっています。
第2回世界将棋AI電竜戦C級で優勝したコードです。

GCT電竜を超える将棋AIの創り方は、dlshogiライブラリの使い方の解説をしていて、書籍向けに用意したデータセットを使って、Colab(V100)で1日程度の学習でGCTと同等のモデルが学習できます。
Colabの無料は最近はK80しか引けないので、その場合、さすがに1日では無理なので学習済みモデルも用意しています。
発展内容として、強化学習や定跡作成の方法も紹介しています。

書籍のモデルの強さ

書籍に付属している学習済みモデルの強さは、以下の通りです。たややん互角局面集使用。持ち時間1分、1手1秒加算。

   # PLAYER                          :  RATING  ERROR  POINTS  PLAYED   (%)  CFS(%)    W    D    L  D(%)
   1 model_resnet10_swish_v10-072    :    28.0   21.8   264.5     474    56      98  248   33  193     7
   2 suisho4-8th                     :   -11.3   21.5   224.5     471    48      61  212   25  234     5
   3 model-0000167                   :   -16.7   21.6   222.0     477    47     ---  209   26  242     5

model_resnet10_swish_v10-072が書籍のモデル(GPU V100x2、GPUごと3スレッド)、
suisho4-8thは、水匠4(8スレッド)、
model-0000167は、第1回電竜戦のGCT(GPU V100x2、GPUごと3スレッド)です。

サポート情報(非公式)

書籍の内容にいくつか誤りがありましたので、正誤表をWikiに記載しています。
公式には出版社のサポートページに掲載いただく予定です。
Home · TadaoYamaoka/ShogiAIBook2 Wiki · GitHub


ということで、書籍の方よろしくお願いいたします。
https://www.amazon.co.jp/gp/product/B09KNGV1QT/ref=as_li_qf_asin_il_tl?ie=UTF8&tag=87etg84cev00-22&creative=1211&linkCode=as2&creativeASIN=B09KNGV1QT&linkId=e5e2cbf4aa4e1c0d8220fcbec6a0f4ecwww.amazon.co.jp