DiSMEC++
statistics.cpp
Go to the documentation of this file.
1 // Copyright (c) 2021, Aalto University, developed by Erik Schultheis
2 // All rights reserved.
3 //
4 // SPDX-License-Identifier: MIT
5 
6 #include "statistics.h"
7 #include "solver/minimizer.h"
8 #include "stats/collection.h"
9 #include "stats/statistics.h"
10 #include "parallel/thread_id.h"
11 #include "initializer.h"
12 #include "spec.h"
13 #include "data/data.h"
14 #include "utils/conversion.h"
15 #include <fstream>
16 #include <iomanip>
17 #include <nlohmann/json.hpp>
18 
19 using namespace dismec;
20 
21 TrainingStatsGatherer::TrainingStatsGatherer(std::string source, std::string target_file) :
22  m_TargetFile(std::move(target_file)) {
23  if(source.empty()) {
24  m_Config = std::make_unique<nlohmann::json>();
25  } else {
26  std::fstream source_stream(source, std::fstream::in);
27  m_Config = std::make_unique<nlohmann::json>(nlohmann::json::parse(source_stream));
28  }
29 }
30 
32  add_accu("minimizer", thread, minimizer.get_stats());
33 }
34 
36  add_accu("init", thread, initializer.get_stats());
37 }
38 
40  add_accu("objective", thread, objective.get_stats());
41 }
43  add_accu("post", thread, post.get_stats());
44 }
45 
47  // Outer loop iterates over thread indices.
48  for(auto&& entries : m_PerThreadCollections) {
49  // inner loop iterates over map keys
50  for (auto&& accu : entries) {
51  for (auto&& meta : accu.second->get_statistics_meta()) {
52  if (!accu.second->is_enabled_by_name(meta.Name)) continue;
53  std::string qualified_name = accu.first + '.'+ meta.Name;
54  if (m_Merged.count(qualified_name) == 0) {
55  m_Merged[qualified_name] = {meta, accu.second->get_stat(meta.Name).clone()};
56  }
57 
58  m_Merged.at(qualified_name).Stat->merge(accu.second->get_stat(meta.Name));
59  }
60  }
61  }
62 }
63 
65  if(!m_TargetFile.empty()) {
66  nlohmann::json result = to_json();
67  std::fstream target(m_TargetFile, std::fstream::out);
68  target << std::setw(2) << result << "\n";
69  }
70 }
71 
73  nlohmann::json result;
74  for(const auto& stat : m_Merged) {
75  auto raw = stat.second.Stat->to_json();
76  if(!stat.second.Meta.Unit.empty())
77  raw["Unit"] = stat.second.Meta.Unit;
78  result[stat.first] = std::move(raw);
79  }
80  return result;
81 }
82 
83 
86 
87 namespace {
88  constexpr const stats::stat_id_t STAT_FINAL_LOSS{0};
89  constexpr const stats::stat_id_t STAT_FINAL_GRAD{1};
90  constexpr const stats::stat_id_t STAT_INIT_LOSS{2};
91  constexpr const stats::stat_id_t STAT_INIT_GRAD{3};
92  constexpr const stats::stat_id_t STAT_NUM_ITERS{4};
93  constexpr const stats::stat_id_t STAT_DURATION{5};
95  constexpr const stats::stat_id_t STAT_LABEL_ID{7};
96  constexpr const stats::stat_id_t STAT_LABEL_FREQ{8};
97  constexpr const stats::stat_id_t STAT_INIT_VECTOR{9};
99 
100  constexpr const stats::tag_id_t TAG_LABEL_ID{0};
101  constexpr const stats::tag_id_t TAG_LABEL_FREQ{1};
102 
104  public:
105 
106  explicit DefaultGatherer(const TrainingSpec& spec) : m_Data(&spec.get_data()) {
107  declare_stat(STAT_FINAL_LOSS, {"final_loss", "|g|"});
108  declare_stat(STAT_FINAL_GRAD, {"final_grad", "loss"});
109  declare_stat(STAT_INIT_LOSS, {"initial_loss", "loss"});
110  declare_stat(STAT_INIT_GRAD, {"initial_grad", "|g|"});
111  declare_stat(STAT_NUM_ITERS, {"iters", "#iters"});
112  declare_stat(STAT_DURATION, {"duration", "duration [ms]"});
113  declare_stat(STAT_WEIGHT_VECTOR, {"weights"});
114  declare_stat(STAT_LABEL_ID, {"label_id"});
115  declare_stat(STAT_LABEL_FREQ, {"label_freq"});
116  declare_stat(STAT_INIT_VECTOR, {"initial_weights"});
117  declare_stat(STAT_TRAINING_SHIFT, {"training_shift"});
118  declare_tag(TAG_LABEL_ID, "label");
119  declare_tag(TAG_LABEL_FREQ, "label_freq");
120  }
121 
122 
123  void start_label(label_id_t label) override {
124  long pos = m_Data->num_positives(label);
125  set_tag(TAG_LABEL_ID, label.to_index());
126  set_tag(TAG_LABEL_FREQ, pos);
127  record(STAT_LABEL_ID, label.to_index());
128  record(STAT_LABEL_FREQ, pos);
129  }
130 
131  void start_training(const DenseRealVector& init_weights) override {
132  record(STAT_INIT_VECTOR, init_weights);
133  // if we want to record the shift in the weight vector, we need to cache it here
134  if(get_stats()->is_enabled(STAT_TRAINING_SHIFT)) {
135  if(m_InitWeightsCache)
136  *m_InitWeightsCache = init_weights;
137  else
138  m_InitWeightsCache = std::make_unique<DenseRealVector>(init_weights);
139  }
140  }
141 
142  void record_result(const DenseRealVector& weights, const solvers::MinimizationResult& result) override {
143  record(STAT_FINAL_LOSS, real_t(result.FinalValue));
144  record(STAT_FINAL_GRAD, real_t(result.FinalGrad));
145  record(STAT_INIT_LOSS, real_t(result.InitialValue));
146  record(STAT_INIT_GRAD, real_t(result.InitialGrad));
147  record(STAT_NUM_ITERS, result.NumIters);
148  record(STAT_DURATION, result.Duration.count());
149  record(STAT_WEIGHT_VECTOR, weights);
150 
151  record(STAT_TRAINING_SHIFT, [&]() -> DenseRealVector {
152  return weights - *m_InitWeightsCache;
153  });
154  }
155 
157 
158  std::unique_ptr<DenseRealVector> m_InitWeightsCache;
159  };
160 }
161 
162 std::unique_ptr<ResultStatsGatherer> TrainingStatsGatherer::create_results_gatherer(parallel::thread_id_t thread, const std::shared_ptr<const TrainingSpec>& spec) {
163  auto gather = std::make_unique<DefaultGatherer>(*spec);
164  add_accu("result", thread, gather->get_stats());
165  return gather;
166 }
167 
168 void TrainingStatsGatherer::add_accu(const std::string& key, parallel::thread_id_t thread, const std::shared_ptr<stats::StatisticsCollection>& accumulator) {
169  std::lock_guard<std::mutex> lck{m_Lock};
170  if(thread.to_index() >= ssize(m_PerThreadCollections)) {
171  m_PerThreadCollections.resize(thread.to_index() + 1);
172  }
173  // Iterate over all existing collections. We need the two nested loops to first iterate over
174  // the names, and then
175  for(auto& entry : m_PerThreadCollections.at(thread.to_index())) {
176  accumulator->provide_tags(*entry.second);
177  entry.second->provide_tags(*accumulator);
178  }
179  auto result = m_PerThreadCollections.at(thread.to_index()).emplace(key, accumulator);
180  if(!result.second) {
181  THROW_EXCEPTION(std::runtime_error, "Could not emplace key {} for statistics collection", key);
182  }
183 
184  if(m_Config->contains(key)) {
185  for (auto& entry : m_Config->at(key).items()) {
186  if(!accumulator->has_stat(entry.key())) {
187  spdlog::warn("Statistics {} has been defined in json, but has not been declared", entry.key());
188  continue;
189  }
190  accumulator->register_stat(entry.key(), stats::make_stat_from_json(entry.value()));
191  }
192  }
193 }
void start_training(const DenseRealVector &init_weights) override
Definition: statistics.cpp:131
void record_result(const DenseRealVector &weights, const solvers::MinimizationResult &result) override
Definition: statistics.cpp:142
std::unique_ptr< DenseRealVector > m_InitWeightsCache
Definition: statistics.cpp:158
This class gathers the setting-specific parts of the training process.
Definition: spec.h:24
void add_accu(const std::string &key, thread_id_t thread, const std::shared_ptr< stats::StatisticsCollection > &accumulator)
Definition: statistics.cpp:168
void setup_postproc(thread_id_t thread, stats::Tracked &objective)
Definition: statistics.cpp:42
std::unique_ptr< nlohmann::json > m_Config
Definition: statistics.h:61
void setup_minimizer(thread_id_t thread, stats::Tracked &minimizer)
NOTE: these functions will be called concurrently.
Definition: statistics.cpp:31
void setup_initializer(thread_id_t thread, stats::Tracked &initializer)
Definition: statistics.cpp:35
nlohmann::json to_json() const
Definition: statistics.cpp:72
std::vector< std::unordered_map< std::string, collection_ptr_t > > m_PerThreadCollections
Definition: statistics.h:53
void setup_objective(thread_id_t thread, stats::Tracked &objective)
Definition: statistics.cpp:39
std::unordered_map< std::string, StatData > m_Merged
Definition: statistics.h:50
TrainingStatsGatherer(std::string source, std::string target_file)
Definition: statistics.cpp:21
std::unique_ptr< ResultStatsGatherer > create_results_gatherer(thread_id_t thread, const std::shared_ptr< const TrainingSpec > &spec)
Definition: statistics.cpp:162
Strong typedef for an int to signify a label id.
Definition: types.h:20
constexpr T to_index() const
! Explicitly convert to an integer.
Definition: opaque_int.h:32
Strong typedef for an int to signify a thread id.
Definition: thread_id.h:20
A base class to be used for all types that implement some for of statistics tracking.
Definition: tracked.h:42
std::shared_ptr< StatisticsCollection > get_stats() const
Gets an ownership-sharing reference to the StatisticsCollection.
Definition: tracked.h:65
nlohmann::json json
Definition: model-io.cpp:22
constexpr const stats::tag_id_t TAG_LABEL_ID
Definition: statistics.cpp:100
constexpr const stats::stat_id_t STAT_FINAL_LOSS
Definition: statistics.cpp:88
constexpr const stats::stat_id_t STAT_LABEL_ID
Definition: statistics.cpp:95
constexpr const stats::stat_id_t STAT_INIT_LOSS
Definition: statistics.cpp:90
constexpr const stats::stat_id_t STAT_TRAINING_SHIFT
Definition: statistics.cpp:98
constexpr const stats::stat_id_t STAT_WEIGHT_VECTOR
Definition: statistics.cpp:94
constexpr const stats::stat_id_t STAT_INIT_VECTOR
Definition: statistics.cpp:97
constexpr const stats::stat_id_t STAT_LABEL_FREQ
Definition: statistics.cpp:96
constexpr const stats::stat_id_t STAT_FINAL_GRAD
Definition: statistics.cpp:89
constexpr const stats::stat_id_t STAT_DURATION
Definition: statistics.cpp:93
constexpr const stats::stat_id_t STAT_INIT_GRAD
Definition: statistics.cpp:91
constexpr const stats::stat_id_t STAT_NUM_ITERS
Definition: statistics.cpp:92
constexpr const stats::tag_id_t TAG_LABEL_FREQ
Definition: statistics.cpp:101
std::unique_ptr< stats::Statistics > make_stat_from_json(const nlohmann::json &source)
Generates a stats::Statistics object based on a json configuration.
Definition: statistics.cpp:218
Main namespace in which all types, classes, and functions are defined.
Definition: app.h:15
constexpr auto ssize(const C &c) -> std::common_type_t< std::ptrdiff_t, std::make_signed_t< decltype(c.size())>>
signed size free function. Taken from https://en.cppreference.com/w/cpp/iterator/size
Definition: conversion.h:42
types::DenseVector< real_t > DenseRealVector
Any dense, real values vector.
Definition: matrix_types.h:40
float real_t
The default type for floating point values.
Definition: config.h:17
std::chrono::milliseconds Duration
Definition: minimizer.h:31
#define THROW_EXCEPTION(exception_type,...)
Definition: throw_error.h:16