From 265dc92a188b6db99667b7041d467b2921df39b8 Mon Sep 17 00:00:00 2001 From: QueensGambit Date: Sun, 30 May 2021 17:51:53 +0200 Subject: [PATCH 1/3] added material count feature --- .../v2/input_representation.py | 32 +++++++++++++------ .../src/domain/variants/constants.py | 2 +- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/DeepCrazyhouse/src/domain/variants/classical_chess/v2/input_representation.py b/DeepCrazyhouse/src/domain/variants/classical_chess/v2/input_representation.py index f645c1f9..f0d486ee 100644 --- a/DeepCrazyhouse/src/domain/variants/classical_chess/v2/input_representation.py +++ b/DeepCrazyhouse/src/domain/variants/classical_chess/v2/input_representation.py @@ -4,7 +4,7 @@ @project: CrazyAra @author: queensgambit -Input representation for chess v2.1. +Input representation for chess v2.8. This presentation avoids potential overfitting and bias, e.g. no color information, no move counter, no progress counter and adds features which are hard for the CNN to extract, e.g. material info, number legal moves, checkerboard, opposite color bishops. @@ -25,11 +25,12 @@ CHANNEL_IS_960 = 19 CHANNEL_PIECE_MASK = 20 CHANNEL_CHECKERBOARD = 22 -CHANNEL_MATERIAL = 23 +CHANNEL_MATERIAL_DIFF = 23 CHANNEL_OPP_BISHOPS = 28 CHANNEL_CHECKERS = 29 CHANNEL_CHECK_MOVES = 30 CHANNEL_MOBILITY = 32 +CHANNEL_MATERIAL_COUNT = 33 def board_to_planes(board: chess.Board, normalize=True, last_moves=None): @@ -84,13 +85,14 @@ def board_to_planes(board: chess.Board, normalize=True, last_moves=None): Checkers | 1 | Indicates all pieces giving check | Checking Moves | 2 | Indicates all checking moves (from sq, to sq) | Mobility | 1 | Indicates the number of legal moves + P1 Material Count | 5 | (pieces are ordered: PAWN, KNIGHT, BISHOP, ROOK, QUEEN), normalized with 8 | --- - 13 planes + 18 planes The total number of planes is calculated as follows: # -------------- - 13 + 4 + 2 + 1 + 13 - Total: 33 planes + 13 + 4 + 2 + 1 + 18 + Total: 38 planes :param board: Board handle (Python-chess object) :param normalize: True if the inputs shall be normalized to the range [0.-1.] @@ -193,10 +195,10 @@ def board_to_planes(board: chess.Board, normalize=True, last_moves=None): # Channel: 23 - 27 # Relative material difference (negative if less pieces than opponent and positive if more) # iterate over all pieces except the king - assert(channel == CHANNEL_MATERIAL) + assert(channel == CHANNEL_MATERIAL_DIFF) for piece_type in chess.PIECE_TYPES[:-1]: - matt_diff = len(board.pieces(piece_type, me)) - len(board.pieces(piece_type, you)) - planes[channel, :, :] = matt_diff / NORMALIZE_PIECE_NUMBER if normalize else matt_diff + material_count = len(board.pieces(piece_type, me)) - len(board.pieces(piece_type, you)) + planes[channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count channel += 1 # Channel: 28 @@ -236,6 +238,14 @@ def board_to_planes(board: chess.Board, normalize=True, last_moves=None): planes[channel, :, :] = len(my_legal_moves) / NORMALIZE_MOBILITY if normalize else len(my_legal_moves) channel += 1 + # Channel: 33 + # Material + assert(channel == CHANNEL_MATERIAL_COUNT) + for piece_type in chess.PIECE_TYPES[:-1]: + material_count = len(board.pieces(piece_type, me)) + planes[channel, :, :] = material_count / NORMALIZE_PIECE_NUMBER if normalize else material_count + channel += 1 + assert channel == NB_CHANNELS_TOTAL return planes @@ -329,10 +339,14 @@ def normalize_input_planes(planes): :param planes: Input planes representation :return: The normalized planes """ - channel = CHANNEL_MATERIAL + channel = CHANNEL_MATERIAL_DIFF for _ in chess.PIECE_TYPES[:-1]: planes[channel, :, :] /= NORMALIZE_PIECE_NUMBER channel += 1 planes[CHANNEL_MOBILITY, :, :] /= NORMALIZE_MOBILITY + channel = CHANNEL_MATERIAL_COUNT + for _ in chess.PIECE_TYPES[:-1]: + planes[channel, :, :] /= NORMALIZE_PIECE_NUMBER + channel += 1 return planes diff --git a/DeepCrazyhouse/src/domain/variants/constants.py b/DeepCrazyhouse/src/domain/variants/constants.py index d8e9014b..2b4e99e9 100644 --- a/DeepCrazyhouse/src/domain/variants/constants.py +++ b/DeepCrazyhouse/src/domain/variants/constants.py @@ -139,7 +139,7 @@ if VERSION == 1: NB_CHANNELS_POS = 15 else: # VERSION == 2 - NB_CHANNELS_POS = 12 + 1 + 13 # 12 pieces + 1 en-passant and 13 auxiliary + NB_CHANNELS_POS = 12 + 1 + 18 # 13 # 12 pieces + 1 en-passant and 13 auxiliary if VERSION == 1: NB_CHANNELS_CONST = 7 else: # VERSION == 2 From 20c3cd275278487fd4634ab94d2e347fd2034ae9 Mon Sep 17 00:00:00 2001 From: QueensGambit Date: Sun, 30 May 2021 17:56:06 +0200 Subject: [PATCH 2/3] added get_plane_statistics.ipynb --- .../preprocessing/get_plane_statistics.ipynb | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 DeepCrazyhouse/src/preprocessing/get_plane_statistics.ipynb diff --git a/DeepCrazyhouse/src/preprocessing/get_plane_statistics.ipynb b/DeepCrazyhouse/src/preprocessing/get_plane_statistics.ipynb new file mode 100644 index 00000000..578f9d8c --- /dev/null +++ b/DeepCrazyhouse/src/preprocessing/get_plane_statistics.ipynb @@ -0,0 +1,129 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Get Plane Statistics (Unit-Test Preparation)\n", + "\n", + "* file: get_plane_statistics.ipynb\n", + "* brief: Allows investigating the board planes and their statistics. These can later be used e.g. for unit-tests.\n", + "\n", + "* author: QueensGambit\n", + "* contact: johannes.czech@cs.tu-darmstadt.de\n", + "* versions:\n", + " * 2021-05-30 initial version" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%load_ext autoreload\n", + "%autoreload 2\n", + "%reload_ext autoreload" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import sys, os\n", + "sys.path.insert(0,'../../../')\n", + "import os\n", + "import sys\n", + "import chess\n", + "import logging\n", + "from DeepCrazyhouse.src.preprocessing.pgn_to_planes_converter import PGN2PlanesConverter\n", + "from DeepCrazyhouse.src.runtime.color_logger import enable_color_logging\n", + "from DeepCrazyhouse.src.domain.variants.input_representation import board_to_planes, get_planes_statistics\n", + "enable_color_logging()\n", + "logging.getLogger().setLevel(logging.WARNING)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "b = chess.Board(\"r3k1nr/pbp4p/p2p2pb/4P3/3P4/N2q1n2/PPP2PPP/5K1R w kq - 0 14\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_planes_statistics(b, True, [])" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "b = chess.Board()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "b.push_uci(\"e2e4\")\n", + "b.push_uci(\"c7c5\")\n", + "b.push_uci(\"d2d3\")\n", + "b.push_uci(\"a7a6\")\n", + "b.push_uci(\"e4e5\")\n", + "b.push_uci(\"d7d5\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "b" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "get_planes_statistics(b, False, [chess.Move.from_uci(\"d7d5\")])" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.7.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} From b49b7f17ac3f526231a92256ebe4588191f91a6b Mon Sep 17 00:00:00 2001 From: QueensGambit Date: Sun, 30 May 2021 18:00:08 +0200 Subject: [PATCH 3/3] added C++ implementation for inputs 2.8 added C++ unit tests --- engine/CMakeLists.txt | 5 +- .../chess_related/inputrepresentation.cpp | 43 +++++++++--- engine/tests/tests.cpp | 65 ++++++++++++++++++- 3 files changed, 101 insertions(+), 12 deletions(-) diff --git a/engine/CMakeLists.txt b/engine/CMakeLists.txt index 64558244..7bb6cc2f 100644 --- a/engine/CMakeLists.txt +++ b/engine/CMakeLists.txt @@ -31,8 +31,9 @@ if (MODE_CHESS) project(ClassicAra CXX) add_definitions(-DMODE_CHESS) add_definitions(-DMCTS_TB_SUPPORT) - add_definitions(-DVERSION=1) -# add_definitions(-DVERSION=2) +# add_definitions(-DVERSION=1) + add_definitions(-DVERSION=2) + add_definitions(-DSUB_VERSION=8) endif() if (MODE_LICHESS) diff --git a/engine/src/environments/chess_related/inputrepresentation.cpp b/engine/src/environments/chess_related/inputrepresentation.cpp index d2bdf005..5b01fd71 100644 --- a/engine/src/environments/chess_related/inputrepresentation.cpp +++ b/engine/src/environments/chess_related/inputrepresentation.cpp @@ -375,18 +375,31 @@ inline void set_mobility(PlaneData& p, const vector& legalMoves) p.set_plane_to_value(p.normalize ? legalMoves.size() / StateConstants::NORMALIZE_MOBILITY() : legalMoves.size()); } -inline void set_opposite_bishops(PlaneData& p) { +inline void set_opposite_bishops(PlaneData& p) +{ if (p.pos->opposite_bishops()) { p.set_plane_to_one(); } ++p.currentChannel; } +inline void set_material_count(PlaneData& p) +{ + float relativeCount = p.pos->count(p.me()); + p.set_plane_to_value(p.normalize ? relativeCount / StateConstants::NORMALIZE_PIECE_NUMBER() : relativeCount); + relativeCount = p.pos->count(p.me()); + p.set_plane_to_value(p.normalize ? relativeCount / StateConstants::NORMALIZE_PIECE_NUMBER() : relativeCount); + relativeCount = p.pos->count(p.me()); + p.set_plane_to_value(p.normalize ? relativeCount / StateConstants::NORMALIZE_PIECE_NUMBER() : relativeCount); + relativeCount = p.pos->count(p.me()); + p.set_plane_to_value(p.normalize ? relativeCount / StateConstants::NORMALIZE_PIECE_NUMBER() : relativeCount); + relativeCount = p.pos->count(p.me()); + p.set_plane_to_value(p.normalize ? relativeCount / StateConstants::NORMALIZE_PIECE_NUMBER() : relativeCount); +} + #ifdef MODE_CHESS -void board_to_planes_v_2_7(const Board *pos, bool normalize, float *inputPlanes, const vector& legalMoves) +inline void board_to_planes_v_2_7(PlaneData& planeData, const vector& legalMoves) { - // Fill in the piece positions - PlaneData planeData(pos, inputPlanes, normalize); set_plane_pieces(planeData); set_plane_ep_square(planeData); assert(planeData.currentChannel == StateConstants::NB_CHANNELS_POS()); @@ -403,20 +416,32 @@ void board_to_planes_v_2_7(const Board *pos, bool normalize, float *inputPlanes, set_checkers(planeData); set_check_moves(planeData, legalMoves); set_mobility(planeData, legalMoves); - assert(planeData.currentChannel == StateConstants::NB_CHANNELS_TOTAL()); } + +inline void board_to_planes_v_2_8(PlaneData& planeData, const vector& legalMoves) +{ + board_to_planes_v_2_7(planeData, legalMoves); + set_material_count(planeData); +} + #endif void board_to_planes(const Board *pos, size_t boardRepetition, bool normalize, float *inputPlanes, const vector& legalMoves) { -#if VERSION == 2 - board_to_planes_v_2_7(pos, normalize, inputPlanes, legalMoves); - return; -#endif // Fill in the piece positions // Iterate over both color starting with WHITE PlaneData planeData(pos, inputPlanes, normalize); +#if VERSION == 2 +#if SUB_VERSION == 7 + board_to_planes_v_2_7(planeData, legalMoves); +#elif SUB_VERSION == 8 + board_to_planes_v_2_8(planeData, legalMoves); +#endif + assert(planeData.currentChannel == StateConstants::NB_CHANNELS_TOTAL()); + return; +#endif + // (I) Set the pieces for both players set_plane_pieces(planeData); diff --git a/engine/tests/tests.cpp b/engine/tests/tests.cpp index 3435a673..c733a5b4 100644 --- a/engine/tests/tests.cpp +++ b/engine/tests/tests.cpp @@ -313,7 +313,7 @@ TEST_CASE("Chess_Input_Planes Version 1"){ REQUIRE(key == 909458); } #else // Version == 2 -TEST_CASE("Chess_Input_Planes Version 2.7"){ +TEST_CASE("Chess_Input_Planes Version 2.7 & 2.8"){ init(); BoardState state; PlaneStatistics stats; @@ -321,78 +321,141 @@ TEST_CASE("Chess_Input_Planes Version 2.7"){ // Start Pos: normalize=false state.init(get_default_variant(), false); stats = get_planes_statistics(state, false); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 2592); + REQUIRE(stats.argMax == 2048); + REQUIRE(stats.maxNum == 20); + REQUIRE(stats.key == 5129584); +#else // SUB_VERSION == 7 REQUIRE(stats.sum == 1632); REQUIRE(stats.argMax == 2048); REQUIRE(stats.maxNum == 20); REQUIRE(stats.key == 3006288); +#endif // Start Pos: normalize=true state.init(get_default_variant(), false); stats = get_planes_statistics(state, true); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 492); + REQUIRE(stats.argMax == 8); + REQUIRE(stats.maxNum == 1); + REQUIRE(stats.key == 651530); +#else REQUIRE(stats.sum == 372); REQUIRE(stats.argMax == 8); REQUIRE(stats.maxNum == 1); REQUIRE(stats.key == 386118); +#endif // Checking test: normalize=false state.set("rnbqk1nr/pppp1ppp/8/4p3/1b1PP3/8/PPP2PPP/RNBQKBNR w KQkq - 1 3", false, get_default_variant()); stats = get_planes_statistics(state, false); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 1697); + REQUIRE(stats.argMax == 2112); + REQUIRE(stats.maxNum == 8); + REQUIRE(stats.key == 3268193); +#else REQUIRE(stats.sum == 737); REQUIRE(stats.argMax == 2048); REQUIRE(stats.maxNum == 6); REQUIRE(stats.key == 1144897); +#endif // last moves state.init(get_default_variant(), false); apply_given_moves(state, {"e2e4", "c7c5"}); stats = get_planes_statistics(state, false); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 3234); + REQUIRE(stats.argMax == 2048); + REQUIRE(stats.maxNum == 30); + REQUIRE(stats.key == 6462788.0); +#else REQUIRE(stats.sum == 2274); REQUIRE(stats.argMax == 2048); REQUIRE(stats.maxNum == 30); REQUIRE(stats.key == 4339492.0); +#endif // Checking move test: normalize=true state.set("r1br2k1/p4ppp/2p2n2/Q1b1p3/8/NP3N1P/P1P1BPP1/R1B1K2R b KQ - 0 12", false, get_default_variant()); stats = get_planes_statistics(state, true); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 329); + REQUIRE(stats.argMax == 8); + REQUIRE(stats.maxNum == 1); + REQUIRE(stats.key == 481472); +#else REQUIRE(stats.sum == 241); REQUIRE(stats.argMax == 8); REQUIRE(stats.maxNum == 1); REQUIRE(stats.key == 287212); +#endif // en-passant test: normalize=false state.init(get_default_variant(), false); apply_given_moves(state, {"e2e4", "c7c5", "d2d3", "a7a6", "e4e5", "d7d5"}); stats = get_planes_statistics(state, false); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 3491); + REQUIRE(stats.argMax == 2048); + REQUIRE(stats.maxNum == 34); + REQUIRE(stats.key == 6995937.0); +#else REQUIRE(stats.sum == 2531); REQUIRE(stats.argMax == 2048); REQUIRE(stats.maxNum == 34); REQUIRE(stats.key == 4872641.0); +#endif // en-passant test + check moves test: normalize=true state.init(get_default_variant(), false); string uciMove; apply_given_moves(state, {"e2e4", "c7c5", "e4e5", "d7d5"}); stats = get_planes_statistics(state, false); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 3301); + REQUIRE(stats.argMax == 2048); + REQUIRE(stats.maxNum == 31); + REQUIRE(stats.key == 6600615.0); +#else REQUIRE(stats.sum == 2341); REQUIRE(stats.argMax == 2048); REQUIRE(stats.maxNum == 31); REQUIRE(stats.key == 4477319.0); +#endif // material difference state.set("r3k1nr/pbp4p/p2p2pb/4P3/3P4/N2q1n2/PPP2PPP/5K1R w kq - 0 14", false, get_default_variant()); stats = get_planes_statistics(state, false); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 723); + REQUIRE(stats.argMax == 2112); + REQUIRE(stats.maxNum == 8); + REQUIRE(stats.key == 1404265.0); +#else REQUIRE(stats.sum == 83); REQUIRE(stats.argMax == 1472); REQUIRE(stats.maxNum == 2); REQUIRE(stats.key == 16041.0); +#endif // castle-rights state.set("2kr3r/pbqp1ppp/2n2n2/4b3/4P3/2NPB3/PPP1QPPP/R4RK1 b - - 4 11", false, get_default_variant()); stats = get_planes_statistics(state, true); +#if SUB_VERSION == 8 + REQUIRE(stats.sum == 214); + REQUIRE(stats.argMax == 8); + REQUIRE(stats.maxNum == 1); + REQUIRE(stats.key == 377604.0); +#else REQUIRE(stats.sum == 118); REQUIRE(stats.argMax == 8); REQUIRE(stats.maxNum == 1); REQUIRE(stats.key == 163636.0); +#endif } #endif