1+ /*
2+ This file is part of Leela Chess Zero.
3+ Copyright (C) 2018 The LCZero Authors
4+
5+ Leela Chess is free software: you can redistribute it and/or modify
6+ it under the terms of the GNU General Public License as published by
7+ the Free Software Foundation, either version 3 of the License, or
8+ (at your option) any later version.
9+
10+ Leela Chess is distributed in the hope that it will be useful,
11+ but WITHOUT ANY WARRANTY; without even the implied warranty of
12+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13+ GNU General Public License for more details.
14+
15+ You should have received a copy of the GNU General Public License
16+ along with Leela Chess. If not, see <http://www.gnu.org/licenses/>.
17+ */
18+
19+ #include " analyzer/analyzer.h"
20+ #include " analyzer/table.h"
21+ #include " mcts/search.h"
22+ #include " neural/factory.h"
23+ #include " neural/loader.h"
24+ #include " utils/optionsparser.h"
25+ #include " utils/string.h"
26+
27+ #include < algorithm>
28+ #include < cmath>
29+ #include < iostream>
30+ #include < sstream>
31+ #include < string>
32+ #include < vector>
33+
34+ namespace lczero {
35+ namespace {
36+ const char * kTsvReportStr = " Filename of the tab-separated report file" ;
37+ const char * kTxtReportStr = " Filename of the text report file" ;
38+ const char * kMovesStr = " Moves in UCI format, space separated" ;
39+ const char * kMovesToAnalyzeStr = " Number of (last) moves to analyze" ;
40+ const char * kNodesStr = " (comma separated) How many nodes to calculate" ;
41+ const char * kTrainExamplesStr =
42+ " How many examples of training data to generate" ;
43+ const char * kWeightsStr = " Network weights file path" ;
44+ const char * kNnBackendStr = " NN backend to use" ;
45+ const char * kNnBackendOptionsStr = " NN backend parameters" ;
46+
47+ const char * kAutoDiscover = " <autodiscover>" ;
48+ } // namespace
49+
50+ Analyzer::Analyzer () {
51+ options_parser_.AddContext (" play" );
52+ options_parser_.AddContext (" training" );
53+ play_options_ = &options_parser_.GetOptionsDict (" play" );
54+ play_options_ = &options_parser_.GetOptionsDict (" training" );
55+
56+ options_parser_.Add <StringOption>(kTsvReportStr , " tsv-report" );
57+ options_parser_.Add <StringOption>(kTxtReportStr , " txt-report" );
58+ options_parser_.Add <StringOption>(kMovesStr , " moves" );
59+ options_parser_.Add <IntOption>(kMovesToAnalyzeStr , 1 , 999 , " num-moves" ) = 4 ;
60+ options_parser_.Add <StringOption>(kNodesStr , " nodes-list" ) =
61+ " 10,50,100,200,400,600,800,1200,1600,5000,10000" ;
62+ options_parser_.Add <IntOption>(kTrainExamplesStr , 1 , 999 ,
63+ " training-examples" ) = 10 ;
64+ options_parser_.Add <StringOption>(kWeightsStr , " weights" , ' w' ) =
65+ kAutoDiscover ;
66+
67+ // TODO(mooskagh) Move all that to network factory.
68+ const auto backends = NetworkFactory::Get ()->GetBackendsList ();
69+ options_parser_.Add <ChoiceOption>(kNnBackendStr , backends, " backend" ) =
70+ backends.empty () ? " <none>" : backends[0 ];
71+ options_parser_.Add <StringOption>(kNnBackendOptionsStr , " backend-opts" );
72+
73+ Search::PopulateUciParams (&options_parser_);
74+
75+ // Overriding default options of search.
76+ auto defaults = options_parser_.GetMutableDefaultsOptions ();
77+ defaults->Set <int >(Search::kMiniBatchSizeStr , 1 ); // Minibatch = 1
78+ defaults->Set <bool >(Search::kSmartPruningStr , false ); // No smart pruning
79+ defaults->Set <bool >(Search::kVerboseStatsStr , true ); // Verbose stats
80+ }
81+
82+ void Analyzer::GatherStats (Table3d* table, const Node* root_node,
83+ std::string& col, bool flip) {
84+ uint64_t total_n = 0 ;
85+ float factor = play_options_->Get <float >(Search::kCpuctStr ) *
86+ std::sqrt (std::max (root_node->GetN (), 1u ));
87+ const float parent_q =
88+ -root_node->GetQ (0 ) - play_options_->Get <float >(Search::kFpuReductionStr );
89+
90+ for (Node* node : root_node->Children ()) {
91+ const auto n = node->GetNStarted ();
92+ total_n += n;
93+ const auto u = factor * node->GetU ();
94+ const auto q = node->GetQ (parent_q);
95+ const auto move = node->GetMove (flip).as_string ();
96+ table->Add3dVal (col, move, " N" , std::to_string (n));
97+ table->Add3dVal (col, move, " U" , std::to_string (u));
98+ table->Add3dVal (col, move, " Q" , std::to_string (q));
99+ table->Add3dVal (col, move, " U+Q" , std::to_string (u + q));
100+ }
101+
102+ for (Node* node : root_node->Children ()) {
103+ auto n = node->GetNStarted ();
104+ table->Add3dVal (col, node->GetMove (flip).as_string (), " N%" ,
105+ std::to_string (static_cast <double >(n) / total_n));
106+ }
107+ }
108+
109+ void Analyzer::RunOnePosition (const std::vector<Move>& moves) {
110+ NNCache cache (200000 );
111+ NodeTree tree;
112+ tree.ResetToPosition (ChessBoard::kStartingFen , moves);
113+
114+ auto nodeses = ParseIntList (play_options_->Get <std::string>(kNodesStr ));
115+ std::sort (nodeses.begin (), nodeses.end ());
116+
117+ Table3d table;
118+ std::vector<std::string> cols;
119+
120+ // Run search in increasing number of nodes.
121+ for (int nodes : nodeses) {
122+ WriteToLog (" Nodes: " + std::to_string (nodes));
123+ SearchLimits limits;
124+ limits.visits = nodes;
125+
126+ Search search (tree, network_.get (),
127+ std::bind (&Analyzer::OnBestMove, this , std::placeholders::_1),
128+ std::bind (&Analyzer::OnInfo, this , std::placeholders::_1),
129+ limits, *play_options_, &cache);
130+
131+ search.RunBlocking (1 );
132+
133+ auto col = std::to_string (nodes);
134+ cols.push_back (col);
135+ GatherStats (&table, tree.GetCurrentHead (), col, tree.IsBlackToMove ());
136+ table.AddColVal (col, " bestmove" , search.GetBestMove ().first .as_string ());
137+ }
138+
139+ // Fetch MCTS-agnostic per-move stats P and V.
140+ std::vector<const Node*> nodes;
141+ for (Node* iter : tree.GetCurrentHead ()->Children ()) {
142+ nodes.emplace_back (iter);
143+ }
144+ std::sort (nodes.begin (), nodes.end (), [](const Node* a, const Node* b) {
145+ return a->GetNStarted () > b->GetNStarted ();
146+ });
147+ std::vector<std::string> rows;
148+ for (const Node* node : nodes) {
149+ auto move = node->GetMove (tree.IsBlackToMove ()).as_string ();
150+ rows.push_back (move);
151+ table.AddRowVal (move, " P" , std::to_string (node->GetP ()));
152+ table.AddRowVal (move, " V" , std::to_string (node->GetV ()));
153+ }
154+
155+ // Dump table to log.
156+ auto lines = table.RenderTable (cols, rows, {" N" , " N%" , " U" , " Q" , " U+Q" },
157+ {" P" , " V" }, {" bestmove" });
158+ for (const auto & line : lines) WriteToTsvLog (line);
159+ }
160+
161+ void Analyzer::Run () {
162+ if (!options_parser_.ProcessAllFlags ()) return ;
163+
164+ // Open log files.
165+ if (!play_options_->Get <std::string>(kTxtReportStr ).empty ()) {
166+ log_.open (play_options_->Get <std::string>(kTxtReportStr ).c_str ());
167+ }
168+ if (!play_options_->Get <std::string>(kTsvReportStr ).empty ()) {
169+ tsvlog_.open (play_options_->Get <std::string>(kTsvReportStr ).c_str ());
170+ }
171+
172+ // Load network
173+ InitializeNetwork ();
174+
175+ // Parse moves.
176+ std::vector<std::string> moves_str =
177+ StrSplitAtWhitespace (play_options_->Get <std::string>(kMovesStr ));
178+ std::vector<Move> moves;
179+ for (const auto & move : moves_str) moves.emplace_back (move);
180+
181+ for (int i = 0 ; i < play_options_->Get <int >(kMovesToAnalyzeStr ); ++i) {
182+ // Write position to logs.
183+ std::string initial_pos;
184+ if (moves_str.empty ()) {
185+ WriteToLog (" Position: startpos" );
186+ WriteToTsvLog ({" Startpos." });
187+ } else {
188+ WriteToLog (" Position: moves " + StrJoin (moves_str));
189+ WriteToTsvLog ({" Moves " + StrJoin (moves_str)});
190+ }
191+
192+ // Run Mcts at different depths.
193+ RunOnePosition (moves);
194+
195+ // Run training several times.
196+
197+ if (moves.empty ()) break ;
198+ moves.pop_back ();
199+ moves_str.pop_back ();
200+ WriteToTsvLog ({});
201+ }
202+
203+ // DumpFlags();
204+ }
205+
206+ void Analyzer::InitializeNetwork () {
207+ std::string network_path = play_options_->Get <std::string>(kWeightsStr );
208+ std::string backend = play_options_->Get <std::string>(kNnBackendStr );
209+ std::string backend_options =
210+ play_options_->Get <std::string>(kNnBackendOptionsStr );
211+
212+ std::string net_path = network_path;
213+ if (net_path == kAutoDiscover ) {
214+ net_path = DiscoveryWeightsFile ();
215+ }
216+ Weights weights = LoadWeightsFromFile (net_path);
217+
218+ OptionsDict network_options = OptionsDict::FromString (
219+ backend_options, &options_parser_.GetOptionsDict ());
220+
221+ network_ = NetworkFactory::Get ()->Create (backend, weights, network_options);
222+ }
223+
224+ void Analyzer::WriteToLog (const std::string& line) const {
225+ std::cout << line << std::endl;
226+ if (log_) log_ << line << std::endl;
227+ }
228+
229+ void Analyzer::WriteToTsvLog (const std::vector<std::string>& line) const {
230+ if (!tsvlog_) return ;
231+ bool first = true ;
232+ for (const auto & chunk : line) {
233+ if (first) {
234+ first = false ;
235+ } else {
236+ tsvlog_ << " \t " ;
237+ }
238+ tsvlog_ << chunk;
239+ }
240+ tsvlog_ << std::endl;
241+ }
242+
243+ void Analyzer::OnBestMove (const BestMoveInfo& move) const {
244+ WriteToLog (" BestMove: " + move.bestmove .as_string ());
245+ }
246+
247+ void Analyzer::OnInfo (const ThinkingInfo& info) const {
248+ std::string res = " Info" ;
249+ if (info.depth >= 0 ) res += " depth " + std::to_string (info.depth );
250+ if (info.seldepth >= 0 ) res += " seldepth " + std::to_string (info.seldepth );
251+ if (info.time >= 0 ) res += " time " + std::to_string (info.time );
252+ if (info.nodes >= 0 ) res += " nodes " + std::to_string (info.nodes );
253+ if (info.score ) res += " score cp " + std::to_string (*info.score );
254+ if (info.nps >= 0 ) res += " nps " + std::to_string (info.nps );
255+
256+ if (!info.pv .empty ()) {
257+ res += " pv" ;
258+ for (const auto & move : info.pv ) res += " " + move.as_string ();
259+ }
260+ if (!info.comment .empty ()) res += " string " + info.comment ;
261+ WriteToLog (res);
262+ }
263+
264+ } // namespace lczero
0 commit comments