Add a HalfKA architecture (a product of K - king, and A - any piece) along with all required infrastructure. HalfKA doesn't discriminate kings compared to HalfKP. Keep old architecture as the default one.

This commit is contained in:
Tomasz Sobczyk
2020-10-28 15:24:29 +01:00
committed by nodchip
parent 317fda2516
commit ec9e49e875
10 changed files with 573 additions and 0 deletions

View File

@@ -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 \

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
// 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<Features::Side::kFriend>>;
// Number of input feature dimensions after conversion
constexpr IndexType kTransformedFeatureDimensions = 256;
namespace Layers {
// Define network structure
using InputLayer = InputSlice<kTransformedFeatureDimensions * 2>;
using HiddenLayer1 = ClippedReLU<AffineTransform<InputLayer, 32>>;
using HiddenLayer2 = ClippedReLU<AffineTransform<HiddenLayer1, 32>>;
using OutputLayer = AffineTransform<HiddenLayer2, 1>;
} // namespace Layers
using Network = Layers::OutputLayer;
} // namespace Eval::NNUE
#endif // #ifndef NNUE_HALFA_256X2_32_32_H_INCLUDED

View File

@@ -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"

50
src/nnue/features/a.cpp Normal file
View File

@@ -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

54
src/nnue/features/a.h Normal file
View File

@@ -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_

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
//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 <Side AssociatedKing>
inline IndexType HalfKA<AssociatedKing>::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 <Side AssociatedKing>
void HalfKA<AssociatedKing>::append_active_indices(
const Position& pos,
Color perspective,
IndexList* active) {
Square ksq = orient(
perspective,
pos.square<KING>(
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 <Side AssociatedKing>
void HalfKA<AssociatedKing>::append_changed_indices(
const Position& pos,
Color perspective,
IndexList* removed,
IndexList* added) {
Square ksq = orient(
perspective,
pos.square<KING>(
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<Side::kFriend>;
template class HalfKA<Side::kEnemy>;
} // namespace Eval::NNUE::Features

View File

@@ -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 <http://www.gnu.org/licenses/>.
*/
#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 <Side AssociatedKing>
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<IndexType>(SQUARE_NB) * static_cast<IndexType>(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

View File

@@ -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 <Side AssociatedKing>
inline IndexType HalfRelativeKA<AssociatedKing>::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 <Side AssociatedKing>
inline IndexType HalfRelativeKA<AssociatedKing>::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<Square>((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 <Side AssociatedKing>
void HalfRelativeKA<AssociatedKing>::append_active_indices(
const Position& pos,
Color perspective,
IndexList* active) {
Square ksq = orient(
perspective,
pos.square<KING>(
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 <Side AssociatedKing>
void HalfRelativeKA<AssociatedKing>::append_changed_indices(
const Position& pos,
Color perspective,
IndexList* removed,
IndexList* added) {
Square ksq = orient(
perspective,
pos.square<KING>(
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<Side::kFriend>;
template class HalfRelativeKA<Side::kEnemy>;
} // namespace Eval::NNUE::Features

View File

@@ -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 <Side AssociatedKing>
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_

View File

@@ -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 <Side AssociatedKing>
class Factorizer<HalfKA<AssociatedKing>> {
private:
using FeatureType = HalfKA<AssociatedKing>;
// 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<A>::get_dimensions()},
// kFeaturesHalfRelativeKPK
{true, Factorizer<HalfRelativeKA<AssociatedKing>>::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<TrainingFeature>* training_features) {
// kFeaturesHalfKPK
IndexType index_offset = append_base_feature<FeatureType>(
kProperties[kFeaturesHalfKA], base_index, training_features);
const auto sq_k = static_cast<Square>(base_index / PS_END2);
const auto a = static_cast<IndexType>(base_index % PS_END2);
// kFeaturesPK
index_offset += inherit_features_if_required<A>(
index_offset, kProperties[kFeaturesA], a, training_features);
// kFeaturesHalfRelativeKPK
if (a >= PS_W_PAWN) {
index_offset += inherit_features_if_required<HalfRelativeKA<AssociatedKing>>(
index_offset, kProperties[kFeaturesHalfRelativeKA],
HalfRelativeKA<AssociatedKing>::make_index(sq_k, a),
training_features);
}
else {
index_offset += skip_features(kProperties[kFeaturesHalfRelativeKA]);
}
assert(index_offset == get_dimensions());
}
};
template <Side AssociatedKing>
constexpr FeatureProperties Factorizer<HalfKA<AssociatedKing>>::kProperties[];
} // namespace Eval::NNUE::Features
#endif // #ifndef _NNUE_TRAINER_FEATURES_FACTORIZER_HALF_KA_H_