diff --git a/src/Makefile b/src/Makefile index f2c4d269..45d27ef2 100644 --- a/src/Makefile +++ b/src/Makefile @@ -50,9 +50,12 @@ SRCS = benchmark.cpp bitbase.cpp bitboard.cpp endgame.cpp evaluate.cpp main.cpp nnue/evaluate_nnue.cpp \ nnue/evaluate_nnue_learner.cpp \ nnue/features/half_kp.cpp \ + nnue/features/half_ka.cpp \ nnue/features/half_relative_kp.cpp \ + nnue/features/half_relative_ka.cpp \ nnue/features/k.cpp \ nnue/features/p.cpp \ + nnue/features/a.cpp \ nnue/features/castling_right.cpp \ nnue/features/enpassant.cpp \ nnue/nnue_test_command.cpp \ diff --git a/src/nnue/architectures/halfka_256x2-32-32.h b/src/nnue/architectures/halfka_256x2-32-32.h new file mode 100644 index 00000000..c108ef5d --- /dev/null +++ b/src/nnue/architectures/halfka_256x2-32-32.h @@ -0,0 +1,54 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +// Definition of input features and network structure used in NNUE evaluation function + +#ifndef NNUE_HALFKA_256X2_32_32_H_INCLUDED +#define NNUE_HALFKA_256X2_32_32_H_INCLUDED + +#include "nnue/features/feature_set.h" +#include "nnue/features/half_ka.h" + +#include "nnue/layers/input_slice.h" +#include "nnue/layers/affine_transform.h" +#include "nnue/layers/clipped_relu.h" + +namespace Eval::NNUE { + + // Input features used in evaluation function + using RawFeatures = Features::FeatureSet< + Features::HalfKA>; + + // Number of input feature dimensions after conversion + constexpr IndexType kTransformedFeatureDimensions = 256; + + namespace Layers { + + // Define network structure + using InputLayer = InputSlice; + using HiddenLayer1 = ClippedReLU>; + using HiddenLayer2 = ClippedReLU>; + using OutputLayer = AffineTransform; + + } // namespace Layers + + using Network = Layers::OutputLayer; + +} // namespace Eval::NNUE + +#endif // #ifndef NNUE_HALFA_256X2_32_32_H_INCLUDED diff --git a/src/nnue/evaluate_nnue_learner.cpp b/src/nnue/evaluate_nnue_learner.cpp index 0cd61a41..4de939c5 100644 --- a/src/nnue/evaluate_nnue_learner.cpp +++ b/src/nnue/evaluate_nnue_learner.cpp @@ -7,6 +7,7 @@ #include "trainer/features/factorizer_feature_set.h" #include "trainer/features/factorizer_half_kp.h" +#include "trainer/features/factorizer_half_ka.h" #include "trainer/trainer_feature_transformer.h" #include "trainer/trainer_input_slice.h" #include "trainer/trainer_affine_transform.h" diff --git a/src/nnue/features/a.cpp b/src/nnue/features/a.cpp new file mode 100644 index 00000000..6ceb4efa --- /dev/null +++ b/src/nnue/features/a.cpp @@ -0,0 +1,50 @@ +#include "a.h" +#include "index_list.h" + +// Definition of input feature A of NNUE evaluation function +namespace Eval::NNUE::Features { + + // Orient a square according to perspective (flip rank for black) + inline Square orient(Color perspective, Square s) { + return Square(int(s) ^ (bool(perspective) * SQ_A8)); + } + + // Find the index of the feature quantity from the king position and PieceSquare + inline IndexType A::make_index( + Color perspective, Square s, Piece pc) { + return IndexType(orient(perspective, s) + kpp_board_index[pc][perspective]); + } + + // Get a list of indices with a value of 1 among the features + void A::append_active_indices( + const Position& pos, + Color perspective, + IndexList* active) { + + Bitboard bb = pos.pieces(); + while (bb) { + Square s = pop_lsb(&bb); + active->push_back(make_index(perspective, s, pos.piece_on(s))); + } + } + + // Get a list of indices whose values ​​have changed from the previous one in the feature quantity + void A::append_changed_indices( + const Position& pos, + Color perspective, + IndexList* removed, + IndexList* added) { + + const auto& dp = pos.state()->dirtyPiece; + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(perspective, dp.from[i], pc)); + + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(perspective, dp.to[i], pc)); + } + } + +} // namespace Eval::NNUE::Features diff --git a/src/nnue/features/a.h b/src/nnue/features/a.h new file mode 100644 index 00000000..50a0d8be --- /dev/null +++ b/src/nnue/features/a.h @@ -0,0 +1,54 @@ +#ifndef _NNUE_FEATURES_A_H_ +#define _NNUE_FEATURES_A_H_ + +#include "features_common.h" + +#include "evaluate.h" + +// Definition of input feature A of NNUE evaluation function +// A is a union of P features and K features, so technically the +// same effect can be achieved by including both P and K features +// but it would result in slower index appending because +// P would conditionally exclude K features and vice versa, +// where A doesn't have any conditionals. +namespace Eval::NNUE::Features { + + // Feature P: PieceSquare of pieces other than balls + class A { + public: + // feature quantity name + static constexpr const char* kName = "A"; + + // Hash value embedded in the evaluation function file + static constexpr std::uint32_t kHashValue = 0x7A4C414Cu; + + // number of feature dimensions + static constexpr IndexType kDimensions = PS_END2; + + // The maximum value of the number of indexes whose value is 1 at the same time among the feature values + static constexpr IndexType kMaxActiveDimensions = 32; + + // Timing of full calculation instead of difference calculation + static constexpr TriggerEvent kRefreshTrigger = TriggerEvent::kNone; + + // Get a list of indices with a value of 1 among the features + static void append_active_indices( + const Position& pos, + Color perspective, + IndexList* active); + + // Get a list of indices whose values ​​have changed from the previous one in the feature quantity + static void append_changed_indices( + const Position& pos, + Color perspective, + IndexList* removed, + IndexList* added); + + private: + // Index of a feature for a given piece on some square + static IndexType make_index(Color perspective, Square s, Piece pc); + }; + +} // namespace Eval::NNUE::Features + +#endif // #ifndef _NNUE_FEATURES_UNION_P_K_H_ diff --git a/src/nnue/features/half_ka.cpp b/src/nnue/features/half_ka.cpp new file mode 100644 index 00000000..83e59067 --- /dev/null +++ b/src/nnue/features/half_ka.cpp @@ -0,0 +1,89 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +//Definition of input features HalfKA of NNUE evaluation function + +#include "half_ka.h" +#include "index_list.h" + +namespace Eval::NNUE::Features { + + // Orient a square according to perspective (flip rank for black) + inline Square orient(Color perspective, Square s) { + return Square(int(s) ^ (bool(perspective) * SQ_A8)); + } + + // Find the index of the feature quantity from the king position and PieceSquare + template + inline IndexType HalfKA::make_index( + Color perspective, + Square s, + Piece pc, + Square ksq) { + + return IndexType(orient(perspective, s) + kpp_board_index[pc][perspective] + PS_END2 * ksq); + } + + // Get a list of indices for active features + template + void HalfKA::append_active_indices( + const Position& pos, + Color perspective, + IndexList* active) { + + Square ksq = orient( + perspective, + pos.square( + AssociatedKing == Side::kFriend ? perspective : ~perspective)); + + Bitboard bb = pos.pieces(); + while (bb) { + Square s = pop_lsb(&bb); + active->push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + } + } + + // Get a list of indices for recently changed features + template + void HalfKA::append_changed_indices( + const Position& pos, + Color perspective, + IndexList* removed, + IndexList* added) { + + Square ksq = orient( + perspective, + pos.square( + AssociatedKing == Side::kFriend ? perspective : ~perspective)); + + const auto& dp = pos.state()->dirtyPiece; + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(perspective, dp.from[i], pc, ksq)); + + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(perspective, dp.to[i], pc, ksq)); + } + } + + template class HalfKA; + template class HalfKA; + +} // namespace Eval::NNUE::Features diff --git a/src/nnue/features/half_ka.h b/src/nnue/features/half_ka.h new file mode 100644 index 00000000..2839357e --- /dev/null +++ b/src/nnue/features/half_ka.h @@ -0,0 +1,75 @@ +/* + Stockfish, a UCI chess playing engine derived from Glaurung 2.1 + Copyright (C) 2004-2020 The Stockfish developers (see AUTHORS file) + + Stockfish is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Stockfish is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +*/ + +#ifndef NNUE_FEATURES_HALF_KA_H_INCLUDED +#define NNUE_FEATURES_HALF_KA_H_INCLUDED + +#include "features_common.h" + +#include "evaluate.h" + +//Definition of input features HalfKPK of NNUE evaluation function +namespace Eval::NNUE::Features { + + // Feature HalfKPK: Combination of the position of own king + // and the position of pieces other than kings + template + class HalfKA { + + public: + // Feature name + static constexpr const char* kName = (AssociatedKing == Side::kFriend) ? + "HalfKA(Friend)" : "HalfKA(Enemy)"; + + // Hash value embedded in the evaluation file + static constexpr std::uint32_t kHashValue = + 0x5F134CB9u ^ (AssociatedKing == Side::kFriend); + + // Number of feature dimensions + static constexpr IndexType kDimensions = + static_cast(SQUARE_NB) * static_cast(PS_END2); + + // Maximum number of simultaneously active features + static constexpr IndexType kMaxActiveDimensions = 32; + + // Trigger for full calculation instead of difference calculation + static constexpr TriggerEvent kRefreshTrigger = + (AssociatedKing == Side::kFriend) ? + TriggerEvent::kFriendKingMoved : TriggerEvent::kEnemyKingMoved; + + // Get a list of indices for active features + static void append_active_indices( + const Position& pos, + Color perspective, + IndexList* active); + + // Get a list of indices for recently changed features + static void append_changed_indices( + const Position& pos, + Color perspective, + IndexList* removed, + IndexList* added); + + private: + // Index of a feature for a given king position and another piece on some square + static IndexType make_index(Color perspective, Square s, Piece pc, Square sq_k); + }; + +} // namespace Eval::NNUE::Features + +#endif // #ifndef NNUE_FEATURES_HALF_KA_H_INCLUDED diff --git a/src/nnue/features/half_relative_ka.cpp b/src/nnue/features/half_relative_ka.cpp new file mode 100644 index 00000000..ba3edbcf --- /dev/null +++ b/src/nnue/features/half_relative_ka.cpp @@ -0,0 +1,86 @@ +#include "half_relative_ka.h" +#include "index_list.h" + +//Definition of input features HalfRelativeKA of NNUE evaluation function +namespace Eval::NNUE::Features { + + // Orient a square according to perspective (flip rank for black) + inline Square orient(Color perspective, Square s) { + return Square(int(s) ^ (bool(perspective) * SQ_A8)); + } + + // Find the index of the feature quantity from the ball position and PieceSquare + template + inline IndexType HalfRelativeKA::make_index( + Color perspective, + Square s, + Piece pc, + Square sq_k) { + + const IndexType p = IndexType(orient(perspective, s) + kpp_board_index[pc][perspective]); + return make_index(sq_k, p); + } + + // Find the index of the feature quantity from the ball position and PieceSquare + template + inline IndexType HalfRelativeKA::make_index( + Square sq_k, + IndexType p) { + + constexpr IndexType W = kBoardWidth; + constexpr IndexType H = kBoardHeight; + const IndexType piece_index = (p - PS_W_PAWN) / SQUARE_NB; + const Square sq_p = static_cast((p - PS_W_PAWN) % SQUARE_NB); + const IndexType relative_file = file_of(sq_p) - file_of(sq_k) + (W / 2); + const IndexType relative_rank = rank_of(sq_p) - rank_of(sq_k) + (H / 2); + return H * W * piece_index + H * relative_file + relative_rank; + } + + // Get a list of indices with a value of 1 among the features + template + void HalfRelativeKA::append_active_indices( + const Position& pos, + Color perspective, + IndexList* active) { + + Square ksq = orient( + perspective, + pos.square( + AssociatedKing == Side::kFriend ? perspective : ~perspective)); + + Bitboard bb = pos.pieces(); + while (bb) { + Square s = pop_lsb(&bb); + active->push_back(make_index(perspective, s, pos.piece_on(s), ksq)); + } + } + + // Get a list of indices whose values ​​have changed from the previous one in the feature quantity + template + void HalfRelativeKA::append_changed_indices( + const Position& pos, + Color perspective, + IndexList* removed, + IndexList* added) { + + Square ksq = orient( + perspective, + pos.square( + AssociatedKing == Side::kFriend ? perspective : ~perspective)); + + const auto& dp = pos.state()->dirtyPiece; + for (int i = 0; i < dp.dirty_num; ++i) { + Piece pc = dp.piece[i]; + + if (dp.from[i] != SQ_NONE) + removed->push_back(make_index(perspective, dp.from[i], pc, ksq)); + + if (dp.to[i] != SQ_NONE) + added->push_back(make_index(perspective, dp.to[i], pc, ksq)); + } + } + + template class HalfRelativeKA; + template class HalfRelativeKA; + +} // namespace Eval::NNUE::Features diff --git a/src/nnue/features/half_relative_ka.h b/src/nnue/features/half_relative_ka.h new file mode 100644 index 00000000..f42661e9 --- /dev/null +++ b/src/nnue/features/half_relative_ka.h @@ -0,0 +1,68 @@ +#ifndef _NNUE_FEATURES_HALF_RELATIVE_KA_H_ +#define _NNUE_FEATURES_HALF_RELATIVE_KA_H_ + +#include "features_common.h" + +#include "evaluate.h" + +// Definition of input features HalfRelativeKA of NNUE evaluation function +// K - King +// A - Any piece +// KA - product of K and A +namespace Eval::NNUE::Features { + + // Feature HalfRelativeKA: Relative position of each piece other than the ball based on own ball or enemy ball + template + class HalfRelativeKA { + public: + // feature quantity name + static constexpr const char* kName = (AssociatedKing == Side::kFriend) ? + "HalfRelativeKA(Friend)" : "HalfRelativeKA(Enemy)"; + + // Hash value embedded in the evaluation function file + static constexpr std::uint32_t kHashValue = + 0xA123051Fu ^ (AssociatedKing == Side::kFriend); + + static constexpr IndexType kNumPieceKinds = 6 * 2; + + // width of the virtual board with the ball in the center + static constexpr IndexType kBoardWidth = FILE_NB * 2 - 1; + + // height of a virtual board with balls in the center + static constexpr IndexType kBoardHeight = RANK_NB * 2 - 1; + + // number of feature dimensions + static constexpr IndexType kDimensions = + kNumPieceKinds * kBoardHeight * kBoardWidth; + + // The maximum value of the number of indexes whose value is 1 at the same time among the feature values + static constexpr IndexType kMaxActiveDimensions = 32; + + // Timing of full calculation instead of difference calculation + static constexpr TriggerEvent kRefreshTrigger = + (AssociatedKing == Side::kFriend) ? + TriggerEvent::kFriendKingMoved : TriggerEvent::kEnemyKingMoved; + + // Get a list of indices with a value of 1 among the features + static void append_active_indices( + const Position& pos, + Color perspective, + IndexList* active); + + // Get a list of indices whose values ​​have changed from the previous one in the feature quantity + static void append_changed_indices( + const Position& pos, + Color perspective, + IndexList* removed, + IndexList* added); + + // Find the index of the feature quantity from the ball position and PieceSquare + static IndexType make_index(Square s, IndexType p); + + // Find the index of the feature quantity from the ball position and PieceSquare + static IndexType make_index(Color perspective, Square s, Piece pc, Square sq_k); + }; + +} // namespace Eval::NNUE::Features + +#endif // #ifndef _NNUE_FEATURES_HALF_RELATIVE_KA_H_ diff --git a/src/nnue/trainer/features/factorizer_half_ka.h b/src/nnue/trainer/features/factorizer_half_ka.h new file mode 100644 index 00000000..90bd9d97 --- /dev/null +++ b/src/nnue/trainer/features/factorizer_half_ka.h @@ -0,0 +1,93 @@ +#ifndef _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_ +#define _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_ + +#include "factorizer.h" + +#include "nnue/features/half_ka.h" +#include "nnue/features/a.h" +#include "nnue/features/half_relative_ka.h" + +// Specialization of NNUE evaluation function feature conversion class template for HalfKA +namespace Eval::NNUE::Features { + + // Class template that converts input features into learning features + // Specialization for HalfKA + template + class Factorizer> { + private: + using FeatureType = HalfKA; + + // The maximum value of the number of indexes whose value is 1 at the same time among the feature values + static constexpr IndexType kMaxActiveDimensions = + FeatureType::kMaxActiveDimensions; + + // Type of learning feature + enum TrainingFeatureType { + kFeaturesHalfKA, + kFeaturesA, + kFeaturesHalfRelativeKA, + kNumTrainingFeatureTypes, + }; + + // Learning feature information + static constexpr FeatureProperties kProperties[] = { + // kFeaturesHalfKPK + {true, FeatureType::kDimensions}, + // kFeaturesPK + {true, Factorizer::get_dimensions()}, + // kFeaturesHalfRelativeKPK + {true, Factorizer>::get_dimensions()}, + }; + + static_assert(get_array_length(kProperties) == kNumTrainingFeatureTypes, ""); + + public: + static constexpr std::string get_name() { + return std::string("Factorizer<") + FeatureType::kName + ">"; + } + + static constexpr std::string get_factorizers_string() { + return " - " + get_name(); + } + + // Get the dimensionality of the learning feature + static constexpr IndexType get_dimensions() { + return get_active_dimensions(kProperties); + } + + // Get index of learning feature and scale of learning rate + static void append_training_features( + IndexType base_index, std::vector* training_features) { + + // kFeaturesHalfKPK + IndexType index_offset = append_base_feature( + kProperties[kFeaturesHalfKA], base_index, training_features); + + const auto sq_k = static_cast(base_index / PS_END2); + const auto a = static_cast(base_index % PS_END2); + + // kFeaturesPK + index_offset += inherit_features_if_required( + index_offset, kProperties[kFeaturesA], a, training_features); + + // kFeaturesHalfRelativeKPK + if (a >= PS_W_PAWN) { + index_offset += inherit_features_if_required>( + index_offset, kProperties[kFeaturesHalfRelativeKA], + HalfRelativeKA::make_index(sq_k, a), + training_features); + } + else { + index_offset += skip_features(kProperties[kFeaturesHalfRelativeKA]); + } + + assert(index_offset == get_dimensions()); + } + }; + + template + constexpr FeatureProperties Factorizer>::kProperties[]; + +} // namespace Eval::NNUE::Features + +#endif // #ifndef _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_